import {Component, HostListener, Input, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {EditorNodeContract} from '../EditorNode.contract'; import {EditorService} from '../../../service/editor.service'; import {FlitterSocketConnection, FlitterSocketServerClientTransaction} from '../../../flitter-socket'; import {ApiService} from '../../../service/api.service'; import {debug} from '../../../utility'; import { EditingUserSelect } from '../../wysiwyg/wysiwyg.component'; @Component({ selector: 'editor-norm', templateUrl: './norm.component.html', styleUrls: ['./norm.component.scss'], }) export class NormComponent extends EditorNodeContract implements OnInit, OnDestroy { @ViewChild('editable') editable; @Input() nodeId: string; @Input() editorUUID?: string; public initialValue = 'Double-click to edit...'; protected savedValue = 'Double-click to edit...'; public contents = ''; private dirtyOverride = false; private editorGroupSocket?: FlitterSocketConnection; public editorGroupUsers: Array<{uuid: string, uid: string, display: string, color: string}> = []; public editorGroupId?: string; public editingUserSelections: Array = []; public requestSelectionRefresh = () => this.refreshRemoteSelections(); constructor( public editorService: EditorService, public readonly api: ApiService, ) { super(); this.contents = this.initialValue; this.savedValue = this.initialValue; console.log('Norm editor component', this); } public isDark() { return document.body.classList.contains('dark'); } public isDirty(): boolean | Promise { return this.dirtyOverride || this.contents !== this.savedValue; } public get isReadonly(): boolean { return !this.editorService.canEdit(); } public writeChangesToNode(): void | Promise { this.nodeRec.value = this.contents; this.savedValue = this.contents; } ngOnInit() { this.editorService = this.editorService.getEditor(this.editorUUID); this.editorService.registerNodeEditor(this.nodeId, this).then(() => { if ( !this.node.Value ) { this.node.Value = {}; } if ( this.node.Value.Value ) { this.initialValue = this.node.Value.Value; this.savedValue = this.node.Value.Value; } this.contents = this.initialValue; }); } ngOnDestroy() { debug('ngOnDestroy in Norm editor component'); if ( this.editorGroupSocket && this.editorGroupId ) { debug('Closing editor socket...'); this.editorGroupSocket.socket.close(); } } onContentsChanged(contents: string) { if ( this.contents !== contents ) { this.contents = contents; this.editorService.triggerSave(); } } public needsLoad(): boolean | Promise { return true; } public async performLoad(): Promise { // This is called after the Node record has been loaded. // FIXME need to find a consistent way of doing this on prod/development // FIXME Probably make use of the systemBase, but allow overriding it in the environment // const url = this.api._build_url('socket/norm-editor'); const url = 'ws://localhost:8000/api/v1/socket/norm-editor'; debug(`Norm editor socket URL: ${url}`); const socket = new FlitterSocketConnection(url); socket.controller(this); await socket.on_open(); debug('Connected to norm editor socket', socket); const [transaction2, socket2, { editor_group_id }] = await socket.asyncRequest('join_editor_group', { NodeId: this.node.UUID, PageId: this.page.UUID, }); this.editorGroupSocket = socket; const [transaction3, socket3, users = []] = await socket.asyncRequest('get_editor_group_users', { editor_group_id }); if ( Array.isArray(users) ) { this.editorGroupUsers = users; } this.editorGroupId = editor_group_id; await this.refreshRemoteSelections(); } setEditorGroupUsers(transaction: FlitterSocketServerClientTransaction, socket: any) { if ( Array.isArray(transaction?.incoming?.users) ) { this.editorGroupUsers = transaction.incoming.users; debug('Refreshed norm editor group users.'); transaction.resolve(); } } setEditorGroupSelections(transaction: FlitterSocketServerClientTransaction, socket: any) { if ( Array.isArray(transaction?.incoming?.selections) ) { debug('Got selections', transaction.incoming.selections); this.editingUserSelections = transaction.incoming.selections; transaction.resolve(); } } async onSelectionChanged(selection: { path: string, offset: number }) { if ( this.editorGroupSocket && this.editorGroupId ) { await this.editorGroupSocket.asyncRequest('set_member_selection', { editor_group_id: this.editorGroupId, selection, }); } } async refreshRemoteSelections() { if ( this.editorGroupSocket && this.editorGroupId ) { const [ transaction, _, data ] = await this.editorGroupSocket.asyncRequest('get_selections', { editor_group_id: this.editorGroupId, }); if ( Array.isArray(data?.selections) ) { this.editingUserSelections = data.selections; } } } }