• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { BaseElement, element } from '../../../base-ui/BaseElement';
17import { HdcDeviceManager } from '../../../hdc/HdcDeviceManager';
18import { SpRecordTrace } from '../SpRecordTrace';
19import { DataMessage } from '../../../hdc/message/DataMessage';
20
21@element('sp-web-hdc-shell')
22export class SpWebHdcShell extends BaseElement {
23  private static MAX_DISPLAY_ROWS = 1000;
24  private static MAX_SAVE_SIZE = 2097152;
25  shellDiv: HTMLDivElement | null | undefined;
26  currentScreenRemain: number = 0;
27  private shellCanvas: HTMLCanvasElement | null | undefined;
28  private shellCanvasCtx: CanvasRenderingContext2D | null | undefined;
29  private resultStr = '';
30  private sendCallBack: ((keyboardEvent: KeyboardEvent | string) => void | undefined) | undefined;
31  private startShellDevice = '';
32  private intervalId: number | undefined;
33  private skipFlag: number[] = [7];
34  private clearFlag: number[] = [27, 91, 50, 74, 27, 91, 72];
35  private CRLFFlag: number[] = [13, 13, 10];
36  private startRealTimeFlag: number[] = [27, 91, 115];
37  private endRealTimeFlag: number[] = [27, 91, 117];
38  private clearRealTimeFlag: number[] = [27, 91, 72, 27, 91, 74];
39  private ctrlCFlag: number[] = [13, 10, 35, 32];
40  private points: Point | undefined;
41  private forwardFlag: boolean = false;
42  private cursorIndex: number = 3;
43  private shellStrLength: number = 0;
44  private textY: number = 0;
45  private cursorRow: string = '';
46  private textDecoder: TextDecoder = new TextDecoder();
47  private isDragging: boolean = false;
48  private static TOP_OFFSET = 48;
49  private static FIRST_ROW_OFFSET = 32;
50  private static LAST_ROW_OFFSET = 40;
51  private static MULTI_LINE_FLAG = '<\b';
52  private static LINE_BREAK_LENGTH = 2;
53  private static LEFT_OFFSET = 48;
54  private realTimeResult: string | null | undefined = '';
55  private startRealTime: boolean = false;
56  private prevTextY: number = 0;
57
58  public initElements(): void {
59    this.shellCanvas = this.shadowRoot!.querySelector<HTMLCanvasElement>('#shell_cmd');
60    this.shellCanvasCtx = this.shellCanvas!.getContext('2d');
61    this.shellCanvasCtx!.fillStyle = '#000';
62
63    this.shellCanvasCtx!.fillRect(0, 0, this.shellCanvas!.width, this.shellCanvas!.height);
64    this.shellDiv = this.shadowRoot!.querySelector<HTMLDivElement>('.shell_cmd_div');
65    this.shellCanvasAddMouseListener();
66    this.shellCanvas!.addEventListener('contextmenu', (event) => {
67      event.preventDefault();
68      event.stopPropagation();
69    });
70    this.shellCanvas!.addEventListener('keydown', async (keyboardEvent) => {
71      keyboardEvent.preventDefault();
72      if (keyboardEvent.ctrlKey && keyboardEvent.code === 'KeyC' && this.points) {
73        let rowText: string = this.getSelectedText();
74        this.points = undefined;
75        await navigator.clipboard.writeText(rowText);
76      } else {
77        if (this.sendCallBack) {
78          this.sendCallBack(keyboardEvent);
79        }
80      }
81    }); //@ts-ignore
82    window.subscribe(window.SmartEvent.UI.DeviceConnect, (deviceName: string) => {
83      if (deviceName) {
84        this.hdcShellFocus();
85      }
86    });
87    window.subscribe(window.SmartEvent.UI.DeviceDisConnect, () => {
88      this.clear();
89    });
90    this.shellCanvas!.addEventListener('blur', () => {
91      if (this.intervalId) {
92        window.clearInterval(this.intervalId);
93      }
94      this.shellCanvasCtx!.clearRect(this.shellStrLength, this.textY, 12, 3);
95    });
96    new ResizeObserver(() => {
97      this.resizeCanvas();
98      this.refreshShellPage(true);
99    }).observe(this);
100  }
101
102  resizeCanvas(): void {
103    if (this.shellCanvas !== null && this.shellCanvas !== undefined) {
104      this.shellCanvas.width = this.shellCanvas.clientWidth;
105      this.shellCanvas.height = this.shellCanvas.clientHeight;
106    }
107  }
108
109  clear(): void {
110    this.sendCallBack = undefined;
111    this.resultStr = '';
112    this.cursorRow = '';
113    this.shellCanvasCtx!.clearRect(0, 0, this.shellCanvas!.width, this.shellCanvas!.height);
114    this.shellCanvasCtx!.fillStyle = '#000';
115    this.shellCanvasCtx!.fillRect(0, 0, this.shellCanvas!.width, this.shellCanvas!.height);
116    window.clearInterval(this.intervalId);
117  }
118
119  public hdcShellFocus(): void {
120    HdcDeviceManager.connect(SpRecordTrace.serialNumber).then((connected) => {
121      if (connected) {
122        if (this.sendCallBack && this.startShellDevice === SpRecordTrace.serialNumber) {
123          this.shellCanvas!.focus();
124          this.refreshShellPage(true);
125        } else {
126          this.clear();
127          this.sendCallBack = HdcDeviceManager.startShell((result: DataMessage) => {
128            if (result.channelClose) {
129              this.clear();
130              return;
131            }
132            this.startShellDevice = SpRecordTrace.serialNumber;
133            this.handleHdcRecvData(result);
134          });
135          this.shellCanvas!.focus();
136          this.refreshShellPage(true);
137        }
138      } else {
139        this.clear();
140      }
141    });
142  }
143
144  arrayBufferCompare(compareA: ArrayBuffer, compareB: number[]): boolean {
145    const arrayA = new Uint8Array(compareA);
146    if (arrayA.length === compareB.length) {
147      for (let i = 0; i < arrayA.length; i++) {
148        const dd = arrayA[i];
149        if (dd !== compareB[i]) {
150          return false;
151        }
152      }
153      return true;
154    }
155    return false;
156  }
157
158  getSelectedText(): string {
159    let selectedText = '';
160    let textLines = [...this.finalArr];
161    let startX = this.points!.startX!;
162    let startY = this.points!.startY!;
163    let endX = this.points!.endX!;
164    let endY = this.points!.endY!;
165    let depth = Math.ceil((endY - startY) / 16);
166    let index = 0;
167    for (let i = 0; i < textLines.length; i++) {
168      let line = textLines[i];
169      let x = SpWebHdcShell.LEFT_OFFSET;
170      let textFirstRowY = 16 * i + SpWebHdcShell.FIRST_ROW_OFFSET;
171      let textLastRowY = 16 * i + SpWebHdcShell.LAST_ROW_OFFSET;
172      let textEndY = 16 * i + SpWebHdcShell.TOP_OFFSET;
173      let w = this.shellCanvasCtx!.measureText(line).width;
174      if (
175        (startY < textEndY && endY >= textEndY) ||
176        (startY > textFirstRowY && startY < textEndY) ||
177        (endY > textLastRowY && endY < textEndY)
178      ) {
179        index++;
180        if (index === 1) {
181          if (depth > 1) {
182            selectedText +=
183              line.slice(this.getCurrentLineBackSize(line, startX - x, true)) + (endX < x + w ? '\n' : '');
184          } else {
185            selectedText += `${line.slice(
186              this.getCurrentLineBackSize(line, startX - x, true),
187              this.getCurrentLineBackSize(line, endX - x, false)
188            )}\n`;
189          }
190        } else if (index === depth) {
191          selectedText += `${line.slice(0, this.getCurrentLineBackSize(line, endX - x, false))}\n`;
192        } else {
193          selectedText += `${line}\n`;
194        }
195      }
196    }
197    return selectedText.trim();
198  }
199
200  forwardSelected(startX: number, startY: number, endX: number, endY: number): void {
201    //左边界x为SpWebHdcShell.LEFT_OFFSET,右边界为this.shellCanvas!.width
202    let depth = Math.ceil((endY - startY) / 16);
203    let startPointX = 0;
204    let startPointY = 0;
205    let endPointX = 0;
206    let endPointY = 0;
207    if (depth <= 1) {
208      this.shellCanvasCtx!.fillRect(startX, startY, endX - startX, endY - startY);
209      startPointX = startX;
210      startPointY = startY;
211      endPointX = endX;
212      endPointY = endY;
213    } else {
214      //绘制多行
215      for (let index = 1; index <= depth; index++) {
216        //第一行,绘起始点到canvas右边界矩形
217        if (index === 1) {
218          this.shellCanvasCtx!.fillRect(startX, startY, this.shellCanvas!.width - startX, index * 16);
219          startPointX = startX;
220          startPointY = startY;
221        } else if (index === depth) {
222          //最后一行,canvas左边界到结束点矩形
223          this.shellCanvasCtx!.fillRect(
224            SpWebHdcShell.LEFT_OFFSET,
225            startY + (index - 1) * 16,
226            endX - SpWebHdcShell.LEFT_OFFSET,
227            endY - (startY + (index - 1) * 16)
228          );
229          endPointX = endX;
230          endPointY = endY;
231        } else {
232          //中间行,canvas的左边界到右边界的矩形
233          this.shellCanvasCtx!.fillRect(
234            SpWebHdcShell.LEFT_OFFSET,
235            startY + (index - 1) * 16,
236            this.shellCanvas!.width,
237            16
238          );
239        }
240      }
241    }
242    this.points = { startX: startPointX, startY: startPointY, endX: endPointX, endY: endPointY };
243  }
244
245  getCurrentLineBackSize(currentLine: string, maxBackSize: number, isStart: boolean): number {
246    let fillText = '';
247    let strings = currentLine.split('');
248    for (let index = 0; index < strings.length; index++) {
249      let text = strings[index];
250      if (
251        this.shellCanvasCtx!.measureText(fillText).width < maxBackSize &&
252        this.shellCanvasCtx!.measureText(fillText + text).width >= maxBackSize
253      ) {
254        if (!isStart) {
255          fillText += text;
256        }
257        break;
258      }
259      fillText += text;
260    }
261    return fillText.length;
262  }
263
264  reverseSelected(startX: number, startY: number, endX: number, endY: number): void {
265    //左边界x为SpWebHdcShell.LEFT_OFFSET,右边界为this.shellCanvas!.width
266    let depth = Math.ceil((startY - endY) / 16);
267    let startPointX = 0;
268    let startPointY = 0;
269    let endPointX = 0;
270    let endPointY = 0;
271    if (depth <= 1) {
272      this.shellCanvasCtx!.fillRect(endX, endY, startX - endX, startY - endY);
273      startPointX = endX;
274      startPointY = endY;
275      endPointX = startX;
276      endPointY = startY;
277    } else {
278      //绘制多行
279      for (let index = 1; index <= depth; index++) {
280        //第一行,绘起始点到canvas左边界矩形
281        if (index === 1) {
282          this.shellCanvasCtx!.fillRect(SpWebHdcShell.LEFT_OFFSET, startY - 16, startX - SpWebHdcShell.LEFT_OFFSET, 16);
283          endPointX = startX;
284          endPointY = startY;
285        } else if (index === depth) {
286          //最后一行,canvas右边界到结束点矩形
287          this.shellCanvasCtx!.fillRect(endX, endY, this.shellCanvas!.width - endX, startY - (index - 1) * 16 - endY);
288          startPointX = endX;
289          startPointY = endY;
290        } else {
291          this.shellCanvasCtx!.fillRect(SpWebHdcShell.LEFT_OFFSET, startY - index * 16, this.shellCanvas!.width, 16);
292          this.shellCanvasCtx!.textBaseline = 'middle';
293        }
294      }
295    }
296    this.points = { startX: startPointX, startY: startPointY, endX: endPointX, endY: endPointY };
297  }
298
299  private singleLineToMultiLine(shellStr: string, foundationWidth: number, maxWidth: number): string[] {
300    let result = [];
301    while (shellStr.length * foundationWidth > maxWidth) {
302      let bfb = maxWidth / (shellStr.length * foundationWidth);
303      let cutIndex = Math.floor(shellStr.length * bfb);
304      let ss = shellStr.substring(0, cutIndex);
305      result.push(ss);
306      shellStr = shellStr.substring(cutIndex);
307    }
308    if (shellStr.length > 0) {
309      result.push(shellStr);
310    }
311    return result;
312  }
313
314  private finalArr: Array<string> = [];
315
316  private drawShellPage(resultStrArr: string[]): void {
317    let maxWidth = this.shellCanvas!.width;
318    let foundationWidth = Math.ceil(this.shellCanvasCtx!.measureText(' ').width);
319    for (let index = 0; index < resultStrArr.length - 1; index++) {
320      let shellStr = resultStrArr[index];
321      let strWidth = this.shellCanvasCtx!.measureText(shellStr).width;
322      if (strWidth > maxWidth) {
323        let lines = this.singleLineToMultiLine(shellStr, foundationWidth, maxWidth - SpWebHdcShell.LEFT_OFFSET);
324        this.finalArr.push(...lines);
325      } else {
326        this.finalArr.push(shellStr);
327      }
328    }
329    if (this.finalArr.length > SpWebHdcShell.MAX_DISPLAY_ROWS) {
330      this.finalArr.splice(0, this.finalArr.length - SpWebHdcShell.MAX_DISPLAY_ROWS + 1);
331    }
332    this.shellCanvasCtx!.fillStyle = '#fff';
333    this.shellCanvasCtx!.font = '16px serif';
334    this.textY = SpWebHdcShell.TOP_OFFSET;
335    this.finalArr.push(this.cursorRow);
336    for (let index: number = 0; index < this.finalArr.length; index++) {
337      let shellStr: string = this.finalArr[index];
338      this.textY = SpWebHdcShell.TOP_OFFSET + index * 16;
339      this.shellCanvasCtx!.fillText(shellStr, SpWebHdcShell.LEFT_OFFSET, this.textY);
340    }
341  }
342
343  private drawCursorStyle(): void {
344    if (this.intervalId) {
345      window.clearInterval(this.intervalId);
346    }
347    let needClear = false;
348    this.intervalId = window.setInterval(() => {
349      if (needClear) {
350        needClear = false;
351        this.shellCanvasCtx!.fillStyle = '#000';
352        this.shellCanvasCtx!.fillRect(this.shellStrLength, this.textY, 12, 3);
353      } else {
354        needClear = true;
355        this.shellCanvasCtx!.fillStyle = '#fff';
356        this.shellCanvasCtx!.fillRect(this.shellStrLength, this.textY, 12, 3);
357      }
358    }, 500);
359  }
360
361  refreshShellPage(scroller: boolean): void {
362    try {
363      if (this.resultStr.length === 0 && this.cursorRow.length === 0) {
364        return;
365      }
366      this.shellCanvasCtx!.clearRect(0, 0, this.shellCanvas!.width, this.shellCanvas!.height);
367      this.shellCanvasCtx!.fillStyle = '#000';
368      this.shellCanvasCtx!.fillRect(0, 0, this.shellCanvas!.width, this.shellCanvas!.height);
369      let resultStrArr = this.resultStr.split('\r\n');
370      if (this.realTimeResult !== '') {
371        resultStrArr = (this.resultStr + this.realTimeResult).split('\r\n');
372      }
373      this.finalArr = [];
374      if (this.shellCanvas!.width > 0) {
375        this.drawShellPage(resultStrArr);
376        this.shellStrLength =
377          this.shellCanvasCtx!.measureText(this.cursorRow.slice(0, this.cursorIndex)).width + SpWebHdcShell.LEFT_OFFSET;
378        // 记录前一次滚动条的位置
379        this.prevTextY = this.shellDiv!.scrollTop + this.shellDiv!.clientHeight - 3;
380        if (scroller && this.textY > this.shellDiv!.clientHeight && this.textY > this.prevTextY) {
381          this.shellDiv!.scrollTop = this.textY - this.shellDiv!.clientHeight + 3;
382          this.currentScreenRemain = this.shellDiv!.scrollTop;
383        }
384        this.drawCursorStyle();
385      }
386    } catch (e) {}
387  }
388
389  public initHtml(): string {
390    return `
391    <style>
392    :host{
393        display: block;
394        border-radius: 0 16px 16px 0;
395        width: 100%;
396        position: relative;
397    }
398    .shell_cmd_div {
399        width: 90%;
400        margin-left: 5%;
401        margin-top: 3%;
402        overflow-y: scroll;
403        height: 40rem;
404        background: #000;
405        border: 1px solid var(--dark-color1,#4D4D4D);
406        border-radius: 16px;
407    }
408     ::-webkit-scrollbar{
409       width: 13px;
410       height: 10px;
411       background-color: #FFFFFF;
412     }
413     ::-webkit-scrollbar-track{
414       border-top-right-radius: 16px;
415       border-bottom-right-radius: 16px;
416       background-color: #000000;
417     }
418     ::-webkit-scrollbar-thumb{
419       background: #5A5A5A;
420       border-radius: 6px;
421     }
422    canvas {
423         display: inline-block;
424         outline: none;
425    }
426
427    </style>
428    <div class="shell_cmd_div">
429        <canvas id="shell_cmd" style="width: 100%;height:${16000 + SpWebHdcShell.TOP_OFFSET}px;" tabindex="0"></canvas>
430    </div>
431    `;
432  }
433
434  private refreshCurrentRow(): void {
435    let lastRow: string = this.resultStr;
436    if (this.resultStr.lastIndexOf('\r\n') !== -1) {
437      lastRow = this.resultStr.substring(this.resultStr.lastIndexOf('\r\n') + 2);
438    }
439    let currentRow: string[] = [...lastRow];
440    let result: string[] = [];
441    this.cursorIndex = 0;
442    for (let index: number = 0; index < currentRow.length; index++) {
443      let currentResult: string = currentRow[index];
444      if (currentResult === '\b') {
445        this.cursorIndex--;
446      } else {
447        result[this.cursorIndex] = currentResult;
448        this.cursorIndex++;
449      }
450    }
451    this.cursorRow = result.join('');
452  }
453
454  private handleHdcRecvData(result: DataMessage): void {
455    const resData = result.getData();
456    if (resData) {
457      if (this.arrayBufferCompare(resData, this.skipFlag)) {
458        return;
459      } else if (this.arrayBufferCompare(resData, this.clearFlag)) {
460        this.resultStr = '';
461        this.shellDiv!.scrollTop = 0;
462        this.cursorIndex = 3;
463      } else if (this.arrayBufferCompare(resData, this.CRLFFlag)) {
464        if (this.resultStr.lastIndexOf('\r\n') !== -1) {
465          this.resultStr = this.resultStr.substring(0, this.resultStr.lastIndexOf('\r\n') + 2) + this.cursorRow;
466        } else {
467          this.resultStr = this.cursorRow;
468        }
469        this.resultStr += result.getDataToString();
470        this.cursorIndex = 3;
471      } else {
472        if (this.resultStr.length > SpWebHdcShell.MAX_SAVE_SIZE) {
473          this.resultStr = this.resultStr.substring(this.resultStr.length / 2);
474        }
475        const arrayA = new Uint8Array(resData);
476        if (arrayA[0] === 13 && arrayA[1] !== 10 && arrayA[1] !== 13) {
477          this.hdcRecvEnterAndBracket(arrayA, result);
478        } else if (this.isStartWidthArrayBuffer(arrayA, this.startRealTimeFlag)) {
479          let lastIndex = this.getLastRestorationIndex(arrayA, this.endRealTimeFlag);
480          this.realTimeResult = this.removeTextAndColorSequenceStr(
481            this.textDecoder.decode(arrayA.slice(lastIndex, arrayA.length))
482          );
483          this.startRealTime = true;
484        } else if (this.isStartWidthArrayBuffer(arrayA, this.clearRealTimeFlag)) {
485          this.realTimeResult = this.removeTextAndColorSequenceStr(
486            this.textDecoder.decode(arrayA.slice(6, arrayA.length))
487          );
488          this.startRealTime = true;
489        } else {
490          if (this.isStartWidthArrayBuffer(arrayA, this.ctrlCFlag)) {
491            this.resultStr += this.realTimeResult;
492            this.startRealTime = false;
493          }
494          this.hdcRecvRealMessage(result);
495        }
496      }
497      this.resultStr = this.removeTextAndColorSequenceStr(this.resultStr);
498      this.refreshCurrentRow();
499      this.refreshShellPage(true);
500    }
501  }
502
503  private hdcRecvEnterAndBracket(arrayA: Uint8Array, result: DataMessage): void {
504    const index = this.resultStr.lastIndexOf('\n');
505    const resultStrLength = this.resultStr.length;
506    if (index > -1 && resultStrLength > index) {
507      this.resultStr = this.resultStr.substring(0, index + 1) + this.textDecoder.decode(arrayA.slice(1, arrayA.length));
508    } else {
509      if (this.resultStr.split('\n').length === 1) {
510        const index = this.cursorRow.lastIndexOf('\n');
511        this.cursorRow =
512          this.cursorRow.substring(0, index + 1) + this.textDecoder.decode(arrayA.slice(1, arrayA.length));
513        this.resultStr = this.cursorRow;
514      } else {
515        this.resultStr += result.getDataToString();
516      }
517    }
518    this.realTimeResult = '';
519  }
520
521  private hdcRecvRealMessage(result: DataMessage): void {
522    if (this.startRealTime) {
523      if (result.getDataToString().includes(SpWebHdcShell.MULTI_LINE_FLAG)) {
524        this.realTimeResult += result.getDataToString().substring(result.getDataToString().indexOf('\r'));
525      } else {
526        this.realTimeResult += result.getDataToString();
527      }
528      this.realTimeResult = this.removeTextAndColorSequenceStr(this.realTimeResult!);
529    } else {
530      this.realTimeResult = '';
531      if (result.getDataToString().includes(SpWebHdcShell.MULTI_LINE_FLAG)) {
532        // 获取所有内容,不包括最后一行数据
533        this.resultStr = this.resultStr.substring(
534          0,
535          this.resultStr.lastIndexOf('\r\n') + SpWebHdcShell.LINE_BREAK_LENGTH
536        );
537        // 多行情况不能直接拼接返回数据
538        this.resultStr += result.getDataToString().substring(result.getDataToString().indexOf('\r'));
539      } else {
540        this.resultStr += result.getDataToString();
541      }
542    }
543  }
544
545  private removeTextAndColorSequenceStr(currentStr: string): string {
546    return currentStr.replace(new RegExp(/\x1B\[[0-9;]*[a-zA-Z]/g), '');
547  }
548
549  private isStartWidthArrayBuffer(sourceArray: Uint8Array, compareArray: number[]): boolean {
550    for (let index = 0; index < compareArray.length; index++) {
551      if (sourceArray[index] !== compareArray[index]) {
552        return false;
553      }
554    }
555    return true;
556  }
557
558  private getLastRestorationIndex(sourceArray: Uint8Array, compareArray: number[]): number {
559    let lastIndex = -1;
560    for (let index = sourceArray.length - 1; index >= 0; index--) {
561      if (sourceArray[index] === compareArray[0]) {
562        let isLast = true;
563        for (let j = 1; j < compareArray.length; j++) {
564          if (sourceArray[index + j] !== compareArray[j]) {
565            isLast = false;
566            break;
567          }
568        }
569        if (isLast) {
570          lastIndex = index;
571          break;
572        }
573      }
574    }
575    return lastIndex + compareArray.length;
576  }
577
578  private shellCanvasAddMouseListener(): void {
579    let startX: number;
580    let startY: number;
581    let endX: number;
582    let endY: number;
583    this.shellCanvas!.addEventListener('mousedown', (event) => {
584      if (this.resultStr.length === 0 && this.cursorRow.length === 0) {
585        return;
586      }
587      this.isDragging = true;
588      startX = event.offsetX;
589      startY = event.offsetY;
590      this.refreshShellPage(false);
591    });
592    this.shellCanvas!.addEventListener('mousemove', (event) => {
593      if (!this.isDragging) {
594        return;
595      }
596      if (this.resultStr.length === 0 && this.cursorRow.length === 0) {
597        return;
598      }
599      endX = event.offsetX;
600      endY = event.offsetY;
601      this.refreshShellPage(false);
602      this.points = undefined;
603      this.shellCanvasCtx!.fillStyle = 'rgba(128, 128, 128, 0.5)';
604      if (endY > startY) {
605        this.forwardFlag = true;
606        this.forwardSelected(startX, startY, endX, endY);
607      } else {
608        this.forwardFlag = false;
609        this.reverseSelected(startX, startY, endX, endY);
610      }
611    });
612    this.shellCanvasAddMouseUpListener();
613  }
614
615  private shellCanvasAddMouseUpListener(): void {
616    this.shellCanvas!.addEventListener('mouseup', async (event) => {
617      if (!this.isDragging) {
618        return;
619      }
620      if (this.resultStr.length === 0 && this.cursorRow.length === 0) {
621        return;
622      }
623      this.isDragging = false;
624      //右键
625      if (event.button === 2) {
626        let text: string = await navigator.clipboard.readText();
627        if (text) {
628          if (this.sendCallBack) {
629            this.sendCallBack(text);
630          }
631          return;
632        }
633      }
634    });
635  }
636}
637
638export class Point {
639  startX: number | undefined;
640  startY: number | undefined;
641  endX: number | undefined;
642  endY: number | undefined;
643}
644