diff --git a/core/new-gui/package.json b/core/new-gui/package.json index 365a6d4a465..0f04564dda1 100644 --- a/core/new-gui/package.json +++ b/core/new-gui/package.json @@ -4,7 +4,7 @@ "license": "MIT", "scripts": { "ng": "nx", - "start": "ng serve", + "start": "concurrently --kill-others \"npx y-websocket\" \"ng serve\"", "build": "ng build --configuration production --progress=false --source-map=false", "analyze": "ng build --configuration production --stats-json && webpack-bundle-analyzer dist/stats.json", "test": "ng test", @@ -67,7 +67,7 @@ "jwt-decode": "~3.1.2", "lodash-es": "~4.17.21", "mapbox-gl": "~2.7.0", - "monaco-editor": "~0.32.1", + "monaco-editor": "^0.34.0", "ng-dynamic-component": "~10.1.0", "ng-gapi": "~0.0.94", "ng-zorro-antd": "13.2.2", @@ -78,11 +78,19 @@ "ngx-monaco-editor": "~9.0.0", "ngx-popper": "~7.0.0", "popper.js": "~1.16.1", + "quill": "^1.3.7", + "quill-cursors": "^3.1.2", "ring-buffer-ts": "~1.0.3", "rxjs": "~7.5.5", "tinyqueue": "~2.0.3", "tslib": "~2.3.1", "uuid": "~8.3.2", + "y-indexeddb": "^9.0.7", + "y-monaco": "^0.1.4", + "y-protocols": "^1.0.5", + "y-quill": "^0.1.5", + "y-websocket": "^1.4.0", + "yjs": "^13.5.34", "zone.js": "~0.11.4" }, "devDependencies": { @@ -114,8 +122,10 @@ "@types/json-schema": "~7.0.9", "@types/lodash": "~4.14.179", "@types/uuid": "~8.3.4", + "@types/quill": "^2.0.9", "@typescript-eslint/eslint-plugin": "~5.14.0", "@typescript-eslint/parser": "~5.14.0", + "concurrently": "^7.4.0", "cypress": "~9.5.1", "eslint": "~7.32.0", "eslint-plugin-import": "latest", diff --git a/core/new-gui/proxy.config.json b/core/new-gui/proxy.config.json index dc65b2a7912..3f956b2e992 100755 --- a/core/new-gui/proxy.config.json +++ b/core/new-gui/proxy.config.json @@ -9,5 +9,11 @@ "secure": false, "changeOrigin": false, "ws": true + }, + "/rtc": { + "target": "http://localhost:1234", + "ws": true, + "secure": false, + "changeOrigin": false } } diff --git a/core/new-gui/src/app/common/service/user/stub-user.service.ts b/core/new-gui/src/app/common/service/user/stub-user.service.ts index d2af59fa62c..e5d60e0316d 100644 --- a/core/new-gui/src/app/common/service/user/stub-user.service.ts +++ b/core/new-gui/src/app/common/service/user/stub-user.service.ts @@ -48,4 +48,8 @@ export class StubUserService implements PublicInterfaceOf { userChanged(): Observable { return this.userChangeSubject.asObservable(); } + + getCurrentUser(): User | undefined { + return this.user; + } } diff --git a/core/new-gui/src/app/common/service/user/user.service.ts b/core/new-gui/src/app/common/service/user/user.service.ts index f78426458e3..d87d8c56edc 100644 --- a/core/new-gui/src/app/common/service/user/user.service.ts +++ b/core/new-gui/src/app/common/service/user/user.service.ts @@ -22,6 +22,10 @@ export class UserService { } } + public getCurrentUser(): User | undefined { + return this.currentUser; + } + public login(username: string, password: string): Observable { // validate the credentials with backend return this.authService @@ -56,7 +60,14 @@ export class UserService { * @param user */ private changeUser(user: User | undefined): void { - this.currentUser = user; + if (user) { + const r = Math.floor(Math.random() * 155); + const g = Math.floor(Math.random() * 155); + const b = Math.floor(Math.random() * 155); + this.currentUser = { ...user, color: "rgba(" + r + "," + g + "," + b + ",0.8)" }; + } else { + this.currentUser = user; + } this.userChangeSubject.next(this.currentUser); } diff --git a/core/new-gui/src/app/common/type/user.ts b/core/new-gui/src/app/common/type/user.ts index abaf31e8aa7..1ef8ee9be4b 100644 --- a/core/new-gui/src/app/common/type/user.ts +++ b/core/new-gui/src/app/common/type/user.ts @@ -1,6 +1,8 @@ +import { Point } from "../../workspace/types/workflow-common.interface"; + /** * This interface stores the information about the user account. - * These information is used to identify users and to save their data + * Such information is used to identify users and to save their data * Corresponds to `core/amber/src/main/scala/edu/uci/ics/texera/web/resource/auth/UserResource.scala` */ export interface User @@ -8,4 +10,20 @@ export interface User name: string; uid: number; googleId?: string; + color?: string; }> {} + +/** + * This interface is for user-presence information in shared-editing. + */ +export interface UserState { + user: User; + clientId: String; + isActive: boolean; + userCursor: Point; + highlighted?: string[]; + unhighlighted?: string[]; + currentlyEditing?: string; + changed?: string; + editingCode?: boolean; +} diff --git a/core/new-gui/src/app/workspace/component/code-editor-dialog/code-editor-dialog.component.ts b/core/new-gui/src/app/workspace/component/code-editor-dialog/code-editor-dialog.component.ts index f7de0cfb542..9e51180bb81 100644 --- a/core/new-gui/src/app/workspace/component/code-editor-dialog/code-editor-dialog.component.ts +++ b/core/new-gui/src/app/workspace/component/code-editor-dialog/code-editor-dialog.component.ts @@ -1,7 +1,6 @@ import { Component, Inject } from "@angular/core"; import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; -import { WorkflowCollabService } from "../../service/workflow-collab/workflow-collab.service"; import { WorkflowActionService } from "../../service/workflow-graph/model/workflow-action.service"; import { OperatorPredicate } from "../../types/workflow-common.interface"; @@ -25,30 +24,16 @@ export class CodeEditorDialogComponent { language: "python", fontSize: "11", automaticLayout: true, - readOnly: true, + readOnly: false, }; code: string; - public lockGranted: boolean = false; - constructor( private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) code: any, - private workflowActionService: WorkflowActionService, - private workflowCollabService: WorkflowCollabService + private workflowActionService: WorkflowActionService ) { this.code = code; - this.handleLockChange(); - } - - private handleLockChange(): void { - this.workflowCollabService - .getLockStatusStream() - .pipe(untilDestroyed(this)) - .subscribe((lockGranted: boolean) => { - this.lockGranted = lockGranted; - this.editorOptions.readOnly = !this.lockGranted; - }); } onCodeChange(code: string): void { diff --git a/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.html b/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.html index b728e427dc1..4c2bdd442be 100644 --- a/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.html +++ b/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.html @@ -1,13 +1,5 @@
-
diff --git a/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.scss b/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.scss index 76aa34efcc0..826ec4f5398 100644 --- a/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.scss +++ b/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.scss @@ -4,15 +4,4 @@ .attribute-container { text-align: center; - .code-button { - &.readonly { - color: black; - background-color: blanchedalmond; - border-color: blanchedalmond; - - &:hover { - background-color: #fff0d9; - } - } - } } diff --git a/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.ts b/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.ts index 225247a1185..58f7c07376e 100644 --- a/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.ts +++ b/core/new-gui/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.ts @@ -2,9 +2,6 @@ import { Component } from "@angular/core"; import { FieldType } from "@ngx-formly/core"; import { MatDialog, MatDialogRef } from "@angular/material/dialog"; import { CodeEditorDialogComponent } from "../code-editor-dialog/code-editor-dialog.component"; -import { WorkflowCollabService } from "../../service/workflow-collab/workflow-collab.service"; -import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; -import { WorkflowActionService } from "../../service/workflow-graph/model/workflow-action.service"; /** * CodeareaCustomTemplateComponent is the custom template for 'codearea' type of formly field. @@ -15,56 +12,24 @@ import { WorkflowActionService } from "../../service/workflow-graph/model/workfl * Clicking on the 'Edit code content' button will create a new dialogue with CodeEditorComponent * as its content. The data of this field will be sent to this dialogue, which contains a Monaco editor. */ -@UntilDestroy() @Component({ selector: "texera-codearea-custom-template", templateUrl: "./codearea-custom-template.component.html", styleUrls: ["./codearea-custom-template.component.scss"], }) export class CodeareaCustomTemplateComponent extends FieldType { - lockGranted: boolean = false; dialogRef: MatDialogRef | undefined; - constructor( - public dialog: MatDialog, - public workflowCollabService: WorkflowCollabService, - public workflowActionService: WorkflowActionService - ) { + constructor(public dialog: MatDialog) { super(); - this.handleLockChange(); - this.handleCodeChange(); } + /** + * Opens the code editor. + */ onClickEditor(): void { this.dialogRef = this.dialog.open(CodeEditorDialogComponent, { data: this.formControl?.value || "", }); } - - private handleLockChange(): void { - this.workflowCollabService - .getLockStatusStream() - .pipe(untilDestroyed(this)) - .subscribe((lockGranted: boolean) => { - this.lockGranted = lockGranted; - }); - } - - private handleCodeChange(): void { - this.workflowActionService - .getTexeraGraph() - .getOperatorPropertyChangeStream() - .pipe(untilDestroyed(this)) - .subscribe(({ operator }) => { - if (this.dialogRef != undefined && !this.lockGranted) { - // here the assumption is the operator being edited must be highlighted - const currentOperatorId: string = this.workflowActionService - .getJointGraphWrapper() - .getCurrentHighlightedOperatorIDs()[0]; - if (currentOperatorId === operator.operatorID) { - this.dialogRef.componentInstance.code = operator.operatorProperties["code"]; - } - } - }); - } } diff --git a/core/new-gui/src/app/workspace/component/navigation/navigation.component.html b/core/new-gui/src/app/workspace/component/navigation/navigation.component.html index 608259a5c98..558944c1eff 100644 --- a/core/new-gui/src/app/workspace/component/navigation/navigation.component.html +++ b/core/new-gui/src/app/workspace/component/navigation/navigation.component.html @@ -7,23 +7,6 @@ src="assets/logos/full_logo_small.png?v=1" /> -
- -
-