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 }); diff --git a/yarn.lock b/yarn.lock index ad5b271..157d490 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1034,6 +1034,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash@4.14.191": + version "4.14.191" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" + integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== + "@types/minimatch@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -4739,7 +4744,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.21: +lodash@4.17.21, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==