diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index fef2fd5aa9a..04479621324 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -113,7 +113,6 @@ import { DatasetFileSelectorComponent } from "./workspace/component/dataset-file
import { DatasetVersionSelectorComponent } from "./workspace/component/dataset-version-selector/dataset-version-selector.component";
import { DatasetSelectionModalComponent } from "./workspace/component/dataset-selection-modal/dataset-selection-modal.component";
import { ReActStepDetailModalComponent } from "./workspace/component/agent/agent-panel/react-step-detail-modal/react-step-detail-modal.component";
-import { CollabWrapperComponent } from "./common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component";
import { NzSwitchModule } from "ng-zorro-antd/switch";
import { NzRadioModule } from "ng-zorro-antd/radio";
import { AboutComponent } from "./hub/component/about/about.component";
@@ -341,7 +340,6 @@ registerLocaleData(en);
DatasetVersionSelectorComponent,
DatasetSelectionModalComponent,
ReActStepDetailModalComponent,
- CollabWrapperComponent,
AboutComponent,
UserWorkflowListItemComponent,
UserProjectListItemComponent,
diff --git a/frontend/src/app/common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component.css b/frontend/src/app/common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component.css
deleted file mode 100644
index d0848680345..00000000000
--- a/frontend/src/app/common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component.css
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-:host ::ng-deep .ql-clipboard {
- max-height: 0;
-}
-
-:host ::ng-deep .ql-editor {
- outline: transparent;
- overflow: visible;
- position: relative;
-}
-
-:host ::ng-deep .ql-editor > p {
- margin-bottom: 0;
- box-sizing: border-box;
-}
-
-:host ::ng-deep .ql-container {
- overflow: visible;
-}
diff --git a/frontend/src/app/common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component.html b/frontend/src/app/common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component.html
deleted file mode 100644
index e3c9700fedb..00000000000
--- a/frontend/src/app/common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
diff --git a/frontend/src/app/common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component.ts b/frontend/src/app/common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component.ts
deleted file mode 100644
index ad6c11eabb4..00000000000
--- a/frontend/src/app/common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component.ts
+++ /dev/null
@@ -1,227 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { AfterContentInit, Component, ElementRef, ViewChild } from "@angular/core";
-import { FieldTypeConfig, FieldWrapper, FormlyFieldConfig } from "@ngx-formly/core";
-import { WorkflowActionService } from "../../../../workspace/service/workflow-graph/model/workflow-action.service";
-import { merge } from "lodash";
-import Quill, { Delta, Range } from "quill";
-import Clipboard from "quill/modules/clipboard";
-import * as Y from "yjs";
-import { QuillBinding } from "y-quill";
-import QuillCursors from "quill-cursors";
-import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
-import { NgStyle } from "@angular/common";
-
-// Quill related definitions
-export const COLLAB_DEBOUNCE_TIME_MS = 10;
-
-/**
- * Custom clipboard module that removes rich text formats and newline characters
- */
-class PlainClipboard extends Clipboard {
- override onPaste(range: Range, { text = "" }: { text?: string; html?: string }) {
- const plainText = text.replace(/\n/g, "");
- const delta = new Delta().retain(range.index).delete(range.length).insert(plainText);
- const index = plainText.length + range.index;
- const length = 0;
- this.quill.updateContents(delta, Quill.sources.SILENT);
- this.quill.setSelection(index, length, Quill.sources.SILENT);
- this.quill.scrollSelectionIntoView();
- }
-}
-
-Quill.register(
- {
- "modules/clipboard": PlainClipboard,
- },
- true
-);
-
-Quill.register("modules/cursors", QuillCursors);
-
-/**
- * CollabWrapperComponent is a custom field wrapper that connects a string/textfield typed form field to a collaborative
- * text editor based on Yjs and Quill.
- */
-@UntilDestroy()
-@Component({
- templateUrl: "./collab-wrapper.component.html",
- styleUrls: ["./collab-wrapper.component.css"],
- imports: [NgStyle],
-})
-export class CollabWrapperComponent extends FieldWrapper implements AfterContentInit {
- private quill?: Quill;
- private currentOperatorId: string = "";
- private operatorType: string = "";
- private quillBinding?: QuillBinding;
- private sharedText?: Y.Text;
- @ViewChild("editor", { static: true }) divEditor?: ElementRef;
-
- constructor(private workflowActionService: WorkflowActionService) {
- super();
- }
-
- ngAfterContentInit(): void {
- this.setUpYTextEditor();
- this.formControl.valueChanges.pipe(untilDestroyed(this)).subscribe(value => {
- if (this.sharedText !== undefined && value !== this.sharedText.toJSON()) {
- this.setUpYTextEditor();
- }
- });
- this.registerDisableEditorInteractivityHandler();
- }
-
- private setUpYTextEditor() {
- setTimeout(() => {
- if (this.field.key === undefined || this.field.props === undefined) {
- throw Error(
- `form collab-wrapper field ${this.field} doesn't contain necessary .key and .templateOptions.presetKey attributes`
- );
- } else {
- this.currentOperatorId = this.field.props.currentOperatorId;
- this.operatorType = this.field.props.operatorType;
- let parents = [this.field.key];
- let parent = this.field.parent;
- while (parent?.key !== undefined) {
- parents.push(parent.key);
- parent = parent.parent;
- }
- let parentStructure: any = this.workflowActionService
- .getTexeraGraph()
- .getSharedOperatorPropertyType(this.currentOperatorId);
- let structure: any = undefined;
- let key: any;
- this.workflowActionService.getTexeraGraph().bundleActions(() => {
- while (parents.length > 0 && parentStructure !== undefined && parentStructure !== null) {
- key = parents.pop();
- structure = parentStructure.get(key);
- if (structure === undefined || structure === null) {
- if (parents.length > 0) {
- if (parentStructure instanceof Y.Array) {
- const yArray = parentStructure as Y.Array;
- if (yArray.length > parseInt(key)) {
- yArray.delete(parseInt(key), 1);
- yArray.insert(parseInt(key), [new Y.Map()]);
- } else {
- yArray.push([new Y.Map()]);
- }
- } else {
- parentStructure.set(key as string, new Y.Map());
- }
- } else {
- if (parentStructure instanceof Y.Array) {
- const yArray = parentStructure as Y.Array;
- if (yArray.length > parseInt(key)) {
- yArray.delete(parseInt(key), 1);
- yArray.insert(parseInt(key), [new Y.Text("")]);
- } else {
- yArray.push([new Y.Text("")]);
- }
- } else {
- parentStructure.set(key as string, new Y.Text());
- }
- }
- structure = parentStructure.get(key);
- }
- parentStructure = structure;
- }
- });
- this.sharedText = structure;
- this.initializeQuillEditor();
- if (this.currentOperatorId && this.sharedText) {
- this.quillBinding = new QuillBinding(
- this.sharedText,
- this.quill,
- this.workflowActionService.getTexeraGraph().getSharedModelAwareness()
- );
- }
- }
- }, COLLAB_DEBOUNCE_TIME_MS);
- }
-
- private initializeQuillEditor() {
- // Operator name editor
- const element = this.divEditor as ElementRef;
- this.quill = new Quill(element.nativeElement, {
- modules: {
- cursors: true,
- toolbar: false,
- history: {
- // Local undo shouldn't undo changes
- // from remote users
- userOnly: true,
- },
- // Disable newline on enter and instead quit editing
- keyboard:
- this.field.type === "textarea"
- ? {}
- : {
- bindings: {
- enter: {
- key: 13,
- handler: () => {},
- },
- shift_enter: {
- key: 13,
- shiftKey: true,
- handler: () => {},
- },
- },
- },
- },
- formats: [],
- placeholder: "Start collaborating...",
- theme: "bubble",
- });
- this.quill.enable(this.evaluateInteractivity());
- }
-
- private evaluateInteractivity(): boolean {
- return this.formControl.enabled;
- }
-
- private setInteractivity(interactive: boolean) {
- if (interactive !== this.quill?.isEnabled()) this.quill?.enable(interactive);
- }
-
- private registerDisableEditorInteractivityHandler(): void {
- this.formControl.statusChanges.pipe(untilDestroyed(this)).subscribe(_ => {
- this.setInteractivity(this.evaluateInteractivity());
- });
- }
-
- static setupFieldConfig(
- mappedField: FormlyFieldConfig,
- operatorType: string,
- currentOperatorId: string,
- includePresetWrapper: boolean = false
- ) {
- const fieldConfig: FormlyFieldConfig = {
- wrappers: includePresetWrapper
- ? ["form-field", "preset-wrapper", "collab-wrapper"]
- : ["form-field", "collab-wrapper"],
- props: {
- operatorType: operatorType,
- currentOperatorId: currentOperatorId,
- },
- };
- merge(mappedField, fieldConfig);
- }
-}
diff --git a/frontend/src/app/common/formly/formly-config.ts b/frontend/src/app/common/formly/formly-config.ts
index c4fc54fd77f..fa6888ee325 100644
--- a/frontend/src/app/common/formly/formly-config.ts
+++ b/frontend/src/app/common/formly/formly-config.ts
@@ -25,7 +25,6 @@ import { FormlyFieldConfig } from "@ngx-formly/core";
import { CodeareaCustomTemplateComponent } from "../../workspace/component/codearea-custom-template/codearea-custom-template.component";
import { PresetWrapperComponent } from "./preset-wrapper/preset-wrapper.component";
import { DatasetFileSelectorComponent } from "../../workspace/component/dataset-file-selector/dataset-file-selector.component";
-import { CollabWrapperComponent } from "./collab-wrapper/collab-wrapper/collab-wrapper.component";
import { FormlyRepeatDndComponent } from "./repeat-dnd/repeat-dnd.component";
import { UiUdfParametersComponent } from "../../workspace/component/ui-udf-parameters/ui-udf-parameters.component";
import { DatasetVersionSelectorComponent } from "../../workspace/component/dataset-version-selector/dataset-version-selector.component";
@@ -89,10 +88,7 @@ export const TEXERA_FORMLY_CONFIG = {
{ name: "repeat-section-dnd", component: FormlyRepeatDndComponent },
{ name: "ui-udf-parameters", component: UiUdfParametersComponent, wrappers: ["form-field"] },
],
- wrappers: [
- { name: "preset-wrapper", component: PresetWrapperComponent },
- { name: "collab-wrapper", component: CollabWrapperComponent },
- ],
+ wrappers: [{ name: "preset-wrapper", component: PresetWrapperComponent }],
};
export function minItemsValidationMessage(err: any, field: FormlyFieldConfig) {
diff --git a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
index aba2811444c..b96023abb57 100644
--- a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
+++ b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
@@ -52,7 +52,6 @@ import { SimpleChange } from "@angular/core";
import { cloneDeep } from "lodash-es";
import Ajv from "ajv";
-import { COLLAB_DEBOUNCE_TIME_MS } from "../../../../common/formly/collab-wrapper/collab-wrapper/collab-wrapper.component";
import { FormlyNgZorroAntdModule } from "@ngx-formly/ng-zorro-antd";
import { ComputingUnitStatusService } from "../../../../common/service/computing-unit/computing-unit-status/computing-unit-status.service";
import { MockComputingUnitStatusService } from "../../../../common/service/computing-unit/computing-unit-status/mock-computing-unit-status.service";
@@ -61,6 +60,13 @@ import { DynamicSchemaService } from "../../../service/dynamic-schema/dynamic-sc
import { NotificationService } from "../../../../common/service/notification/notification.service";
import { WorkflowGraph } from "../../../service/workflow-graph/model/workflow-graph";
+/**
+ * Drains the short timers scheduled while the fixture is being set up, before the form change
+ * under test. Unrelated to FORM_DEBOUNCE_TIME_MS, which is what the assertions below actually wait
+ * on; this previously borrowed COLLAB_DEBOUNCE_TIME_MS (also 10ms) from an unrelated component.
+ */
+const SETUP_FLUSH_MS = 10;
+
const { marbles } = configure({ run: false });
describe("Aggregate attribute requirement", () => {
@@ -182,7 +188,7 @@ describe("OperatorPropertyEditFrameComponent", () => {
currentOperatorId: new SimpleChange(undefined, mockScanPredicate.operatorID, true),
});
fixture.detectChanges();
- tick(COLLAB_DEBOUNCE_TIME_MS);
+ tick(SETUP_FLUSH_MS);
// stimulate a form change by the user
const formChangeValue = { tableName: "twitter_sample" };
diff --git a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
index f4b356c4746..211cf52e485 100644
--- a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
+++ b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
@@ -1089,21 +1089,6 @@ export class OperatorPropertyEditFrameComponent implements OnInit, OnChanges, On
);
}
- // TODO: we temporarily disable this due to Yjs update causing issues in Formly.
-
- // if (
- // this.currentOperatorId !== undefined &&
- // ["string", "textarea"].includes(mappedField.type as string) &&
- // (mappedField.key as string) !== "password"
- // ) {
- // CollabWrapperComponent.setupFieldConfig(
- // mappedField,
- // this.workflowActionService.getTexeraGraph().getOperator(this.currentOperatorId).operatorType,
- // this.currentOperatorId,
- // mappedField.wrappers?.includes("preset-wrapper")
- // );
- // }
-
if (this.currentOperatorSchema?.operatorType === "Projection" && mappedField.key === "attributes") {
mappedField.type = "repeat-section-dnd";
mappedField.props = {