/* * Copyright (C) 2022 Huawei Device Co., Ltd. * Licensed 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 { ns2s, Rect, Render } from './ProcedureWorkerCommon'; import { ColorUtils } from '../../component/trace/base/ColorUtils'; import { TraceRow } from '../../component/trace/base/TraceRow'; import { CpuStruct } from './cpu/ProcedureWorkerCPU'; //绘制时间轴 let timeRuler: TimeRuler | undefined; let rangeRuler: RangeRuler | undefined; let sportRuler: SportRuler | undefined; let offsetTop: number = 0; let offsetLeft: number = 0; export class TimelineRender extends Render { renderMainThread(req: any, row: TraceRow<any>) {} } export function timeline( canvas: OffscreenCanvas, ctx: OffscreenCanvasRenderingContext2D, totalNS: number, frame: Rect, keyPressCode: any, keyUpCode: any, mouseDown: any, mouseUp: any, mouseMove: any, mouseOut: any, _offsetLeft: number, _offsetTop: number, changeHandler: Function ) { offsetLeft = _offsetLeft; offsetTop = _offsetTop; if (timeRuler == undefined) { timeRuler = new TimeRuler(canvas, ctx, new Rect(0, 0, frame.width, 20), totalNS); } if (!sportRuler) { sportRuler = new SportRuler(canvas, ctx, new Rect(0, 100.5, frame.width, frame.height - 100)); } if (!rangeRuler) { rangeRuler = new RangeRuler( canvas, ctx!, new Rect(0, 25, frame.width, 75), { startX: 0, endX: frame.width, startNS: 0, endNS: totalNS, totalNS: totalNS, xs: [], xsTxt: [], }, (a) => { if (sportRuler) { sportRuler.range = a; } changeHandler(a); } ); } rangeRuler.frame.width = frame.width; sportRuler.frame.width = frame.width; timeRuler.frame.width = frame.width; timelineMouseEvent(keyPressCode, keyUpCode, mouseDown, mouseUp, mouseMove, mouseOut); } function timelineMouseEvent( keyPressCode: any, keyUpCode: any, mouseDown: any, mouseUp: any, mouseMove: any, mouseOut: any ) { if (keyPressCode) { rangeRuler!.keyPress(keyPressCode); } else if (keyUpCode) { rangeRuler!.keyUp(keyUpCode); } else if (mouseDown) { rangeRuler!.mouseDown(mouseDown); } else if (mouseUp) { rangeRuler!.mouseUp(mouseUp); } else if (mouseMove) { rangeRuler!.mouseMove(mouseMove); } else if (mouseOut) { rangeRuler!.mouseOut(mouseOut); } else { timeRuler!.draw(); rangeRuler!.draw(); } } export abstract class Graph { // @ts-ignore c: OffscreenCanvasRenderingContext2D; // @ts-ignore canvas: OffscreenCanvas | undefined | null; frame: Rect; protected constructor( // @ts-ignore canvas: OffscreenCanvas | undefined | null, // @ts-ignore c: OffscreenCanvasRenderingContext2D, frame: Rect ) { this.canvas = canvas; this.frame = frame; this.c = c; } abstract draw(): void; } export class TimeRuler extends Graph { totalNS: number; private stepSmall: number; private step: number; private stepNS: number; constructor( // @ts-ignore canvas: OffscreenCanvas | undefined | null, // @ts-ignore c: OffscreenCanvasRenderingContext2D, frame: Rect, totalNS: number = 10_000_000_000 ) { super(canvas, c, frame); this.totalNS = totalNS; this.step = this.frame.width / 10; this.stepSmall = this.frame.width / 100; this.stepNS = this.totalNS / 10; } draw() { this.step = this.frame.width / 10; this.stepSmall = this.frame.width / 100; this.stepNS = this.totalNS / 10; this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height); this.c.beginPath(); this.c.strokeStyle = '#999'; this.c.lineWidth = 1; for (let i = 0; i <= 10; i++) { let x = Math.floor(i * this.step) + this.frame.x; this.c.moveTo(x, 0); this.c.lineTo(x, this.frame.height); if (i == 10) break; for (let j = 1; j < 10; j++) { this.c.moveTo(x + Math.floor(j * this.stepSmall), 0); this.c.lineTo(x + Math.floor(j * this.stepSmall), this.frame.height / 4); } this.c.fillStyle = '#999'; this.c.fillText(`${ns2s(i * this.stepNS)}`, x + 5, this.frame.height - 1); } this.c.stroke(); this.c.closePath(); } } /** * SportRuler */ export class SportRuler extends Graph { public static rulerFlagObj: Flag | null = null; public flagList: Array<Flag> = []; public flagListIdx: number | null = null; public obj = [{ x: 3 }, { x: 2 }]; lineColor: string | null = null; private rangeFlag = new Flag(0, 0, 0, 0, 0); private ruler_w = 1022; private _range: TimeRange = {} as TimeRange; constructor( // @ts-ignore canvas: OffscreenCanvas | undefined | null, // @ts-ignore c: OffscreenCanvasRenderingContext2D, frame: Rect ) { super(canvas, c, frame); } get range(): TimeRange { return this._range; } set range(value: TimeRange) { this._range = value; this.draw(); } modifyFlagList(type: string, flag: any = {}) { if (type == 'amend') { if (flag.text && this.flagListIdx !== null) { this.flagList[this.flagListIdx].text = flag.text; } if (flag.color && this.flagListIdx !== null) { this.flagList[this.flagListIdx].color = flag.color; } } else if (type == 'remove') { if (this.flagListIdx !== null) { this.flagList.splice(this.flagListIdx, 1); } } this.draw(); } draw(): void { this.ruler_w = this.frame.width; this.c.clearRect(this.frame.x, this.frame.y, this.frame.width, this.frame.height); this.c.beginPath(); this.lineColor = '#dadada'; this.c.strokeStyle = this.lineColor; //"#dadada" this.c.lineWidth = 1; this.c.moveTo(this.frame.x, this.frame.y); this.c.lineTo(this.frame.x + this.frame.width, this.frame.y); this.c.stroke(); this.c.closePath(); this.c.beginPath(); this.c.lineWidth = 3; this.c.strokeStyle = '#999999'; this.c.moveTo(this.frame.x, this.frame.y); this.c.lineTo(this.frame.x, this.frame.y + this.frame.height); this.c.stroke(); this.c.closePath(); this.c.beginPath(); this.c.lineWidth = 1; this.c.strokeStyle = this.lineColor; //"#999999" this.c.fillStyle = '#999999'; this.c.font = '8px sans-serif'; this.range.xs?.forEach((it, i) => { this.c.moveTo(it, this.frame.y); this.c.lineTo(it, this.frame.y + this.frame.height); this.c.fillText(`+${this.range.xsTxt[i]}`, it + 3, this.frame.y + 12); }); this.c.stroke(); this.c.closePath(); } // drawTheFlag drawTheFlag(x: number, color: string = '#999999', isFill: boolean = false, text: string = '') { this.c.beginPath(); this.c.fillStyle = color; this.c.strokeStyle = color; this.c.moveTo(x, 125); this.c.lineTo(x + 10, 125); this.c.lineTo(x + 10, 127); this.c.lineTo(x + 18, 127); this.c.lineTo(x + 18, 137); this.c.lineTo(x + 10, 137); this.c.lineTo(x + 10, 135); this.c.lineTo(x + 2, 135); this.c.lineTo(x + 2, 143); this.c.lineTo(x, 143); this.c.closePath(); if (isFill) { this.c.fill(); } this.c.stroke(); if (text !== '') { this.c.font = '10px Microsoft YaHei'; const { width } = this.c.measureText(text); this.c.fillStyle = 'rgba(255, 255, 255, 0.8)'; // this.c.fillRect(x + 21, 132, width + 4, 12); this.c.fillStyle = 'black'; this.c.fillText(text, x + 23, 142); this.c.stroke(); } } //éšæœºç”Ÿæˆåå…ä½è¿›åˆ¶é¢œè‰² randomRgbColor() { const letters = '0123456789ABCDEF'; let color = '#'; for (let i = 0; i < 6; i++) { color += letters[Math.floor(Math.random() * 16)]; } return color; } //é¼ æ ‡ç‚¹å‡»ç»˜ç”»æ——åã€ç‚¹å‡»æ——å mouseUp(ev: MouseEvent) {} //选ä¸çš„æ——å onFlagRangeEvent(flagObj: Flag, idx: number) { SportRuler.rulerFlagObj = flagObj; this.flagListIdx = idx; } //é¼ æ ‡ç§»åŠ¨ 绘画旗å mouseMove(ev: MouseEvent) { let x = ev.offsetX - (offsetLeft || 0); let y = ev.offsetY - (offsetTop || 0); if (y >= 50 && y < 200) { this.draw(); if (y >= 123 && y < 142 && x > 0) { let onFlagRange = this.flagList.findIndex((flagObj: Flag) => { let flag_x = Math.round( (this.ruler_w * (flagObj.time - this.range.startNS)) / (this.range.endNS - this.range.startNS) ); return x >= flag_x && x <= flag_x + 18; }); } } } } const markPadding = 5; export class Mark extends Graph { name: string | undefined; inspectionFrame: Rect; private _isHover: boolean = false; constructor( // @ts-ignore canvas: OffscreenCanvas | undefined | null, name: string, // @ts-ignore c: OffscreenCanvasRenderingContext2D, frame: Rect ) { super(canvas, c, frame); this.name = name; this.inspectionFrame = new Rect(frame.x - markPadding, frame.y, frame.width + markPadding * 2, frame.height); } get isHover(): boolean { return this._isHover; } set isHover(value: boolean) { this._isHover = value; } draw(): void { this.c.beginPath(); this.c.lineWidth = 7; this.c.strokeStyle = '#999999'; this.c.moveTo(this.frame.x, this.frame.y); this.c.lineTo(this.frame.x, this.frame.y + this.frame.height / 3); this.c.stroke(); this.c.lineWidth = 1; this.c.strokeStyle = '#999999'; this.c.moveTo(this.frame.x, this.frame.y); this.c.lineTo(this.frame.x, this.frame.y + this.frame.height); this.c.stroke(); this.c.closePath(); } } export interface TimeRange { totalNS: number; startX: number; endX: number; startNS: number; endNS: number; xs: Array<number>; xsTxt: Array<string>; } export class RangeRuler extends Graph { public rangeRect: Rect; public markA: Mark; public markB: Mark; public range: TimeRange; mouseDownOffsetX = 0; mouseDownMovingMarkX = 0; movingMark: Mark | undefined | null; isMouseDown: boolean = false; isMovingRange: boolean = false; isNewRange: boolean = false; markAX: number = 0; markBX: number = 0; isPress: boolean = false; pressFrameId: number = -1; currentDuration: number = 0; centerXPercentage: number = 0; animaStartTime: number | undefined; animTime: number = 100; p: number = 800; private readonly notifyHandler: (r: TimeRange) => void; private scale: number = 0; //缩放级别 private scales: Array<number> = [ 50, 100, 200, 500, 1_000, 2_000, 5_000, 10_000, 20_000, 50_000, 100_000, 200_000, 500_000, 1_000_000, 2_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000, 100_000_000, 200_000_000, 500_000_000, 1_000_000_000, 2_000_000_000, 5_000_000_000, 10_000_000_000, 20_000_000_000, 50_000_000_000, 100_000_000_000, 200_000_000_000, 500_000_000_000, ]; private _cpuUsage: Array<{ cpu: number; ro: number; rate: number }> = []; constructor( // @ts-ignore canvas: OffscreenCanvas | undefined | null, // @ts-ignore c: OffscreenCanvasRenderingContext2D, frame: Rect, range: TimeRange, notifyHandler: (r: TimeRange) => void ) { super(canvas, c, frame); this.range = range; this.notifyHandler = notifyHandler; this.markA = new Mark(canvas, 'A', c, new Rect(range.startX, frame.y, 1, frame.height)); this.markB = new Mark(canvas, 'B', c, new Rect(range.endX, frame.y, 1, frame.height)); this.rangeRect = new Rect(range.startX, frame.y, range.endX - range.startX, frame.height); } set cpuUsage(value: Array<{ cpu: number; ro: number; rate: number }>) { this._cpuUsage = value; this.draw(); } drawCpuUsage() { let maxNum = Math.round(this._cpuUsage.length / 100); let miniHeight = Math.round(this.frame.height / CpuStruct.cpuCount); //æ¯æ ¼é«˜åº¦ let miniWidth = Math.ceil(this.frame.width / 100); //æ¯æ ¼å®½åº¦ for (let i = 0; i < this._cpuUsage.length; i++) { //cpu: 0, ro: 0, rate: 0.987620037556431 let it = this._cpuUsage[i]; this.c.fillStyle = ColorUtils.MD_PALETTE[it.cpu]; this.c.globalAlpha = it.rate; this.c.fillRect(this.frame.x + miniWidth * it.ro, this.frame.y + it.cpu * miniHeight, miniWidth, miniHeight); } } scaleSetting(): void { let l20 = (this.range.endNS - this.range.startNS) / 20; let min = 0; let max = 0; let weight = 0; for (let index = 0; index < this.scales.length; index++) { if (this.scales[index] > l20) { if (index > 0) { min = this.scales[index - 1]; } else { min = 0; } max = this.scales[index]; weight = ((l20 - min) * 1.0) / (max - min); if (weight > 0.243) { this.scale = max; } else { this.scale = min; } break; } } if (this.scale == 0) { this.scale = this.scales[0]; } } draw(discardNotify: boolean = false): void { this.c.clearRect(this.frame.x - markPadding, this.frame.y, this.frame.width + markPadding * 2, this.frame.height); this.c.beginPath(); if (this._cpuUsage.length > 0) { this.drawCpuUsage(); this.c.globalAlpha = 0; } else { this.c.globalAlpha = 1; } //绘制选ä¸åŒºåŸŸ this.drawRangeSelection(); if (this.notifyHandler) { this.range.startX = this.rangeRect.x; this.range.endX = this.rangeRect.x + this.rangeRect.width; this.range.startNS = (this.range.startX * this.range.totalNS) / (this.frame.width || 0); this.range.endNS = (this.range.endX * this.range.totalNS) / (this.frame.width || 0); this.scaleSetting(); let tmpNs = 0; let timeLineYu = this.range.startNS % this.scale; let timeLineRealW = (this.scale * this.frame.width) / (this.range.endNS - this.range.startNS); let timeLineStartX = 0; if (this.range.xs) { this.range.xs.length = 0; } else { this.range.xs = []; } if (this.range.xsTxt) { this.range.xsTxt.length = 0; } else { this.range.xsTxt = []; } if (timeLineYu !== 0) { let firstNodeWidth = ((this.scale - timeLineYu) / this.scale) * timeLineRealW; timeLineStartX += firstNodeWidth; tmpNs += timeLineYu; this.range.xs.push(timeLineStartX); this.range.xsTxt.push(ns2s(tmpNs)); } while (tmpNs < this.range.endNS - this.range.startNS) { timeLineStartX += timeLineRealW; tmpNs += this.scale; this.range.xs.push(timeLineStartX); this.range.xsTxt.push(ns2s(tmpNs)); } if (!discardNotify) { this.notifyHandler(this.range); } } } private drawRangeSelection(): void { this.c.fillStyle = '#ffffff'; this.rangeRect.x = this.markA.frame.x < this.markB.frame.x ? this.markA.frame.x : this.markB.frame.x; this.rangeRect.width = Math.abs(this.markB.frame.x - this.markA.frame.x); this.c.fillRect(this.rangeRect.x, this.rangeRect.y, this.rangeRect.width, this.rangeRect.height); this.c.globalAlpha = 1; this.c.globalAlpha = 0.5; this.c.fillStyle = '#999999'; this.c.fillRect(this.frame.x, this.frame.y, this.rangeRect.x, this.rangeRect.height); this.c.fillRect( this.rangeRect.x + this.rangeRect.width, this.frame.y, this.frame.width - this.rangeRect.width, this.rangeRect.height ); this.c.globalAlpha = 1; this.c.closePath(); this.markA.draw(); this.markB.draw(); } mouseDown(ev: MouseEvent) { let timeLineMouseDownX = ev.offsetX - (offsetLeft || 0); let timeLineMouseDownY = ev.offsetY - (offsetTop || 0); this.isMouseDown = true; this.mouseDownOffsetX = timeLineMouseDownX; if (this.markA.isHover) { this.movingMark = this.markA; this.mouseDownMovingMarkX = this.movingMark.frame.x || 0; } else if (this.markB.isHover) { this.movingMark = this.markB; this.mouseDownMovingMarkX = this.movingMark.frame.x || 0; } else { this.movingMark = null; } if (this.rangeRect.containsWithPadding(timeLineMouseDownX, timeLineMouseDownY, 5, 0)) { this.isMovingRange = true; this.markAX = this.markA.frame.x; this.markBX = this.markB.frame.x; } else if ( this.frame.containsWithMargin(timeLineMouseDownX, timeLineMouseDownY, 20, 0, 0, 0) && !this.rangeRect.containsWithMargin(timeLineMouseDownX, timeLineMouseDownY, 0, markPadding, 0, markPadding) ) { this.isNewRange = true; } } movingRange(maxX: number, x: number): void { let result = x - this.mouseDownOffsetX; let mA = result + this.markAX; let mB = result + this.markBX; if (mA >= 0 && mA <= maxX) { this.markA.frame.x = mA; } else if (mA < 0) { this.markA.frame.x = 0; } else { this.markA.frame.x = maxX; } this.markA.inspectionFrame.x = this.markA.frame.x - markPadding; if (mB >= 0 && mB <= maxX) { this.markB.frame.x = mB; } else if (mB < 0) { this.markB.frame.x = 0; } else { this.markB.frame.x = maxX; } this.markB.inspectionFrame.x = this.markB.frame.x - markPadding; requestAnimationFrame(() => this.draw()); } mouseUp(ev: MouseEvent) { this.isMouseDown = false; this.isMovingRange = false; this.isNewRange = false; this.movingMark = null; } movingNewRange(maxX: number, x: number): void { this.markA.frame.x = this.mouseDownOffsetX; this.markA.inspectionFrame.x = this.mouseDownOffsetX - markPadding; if (x >= 0 && x <= maxX) { this.markB.frame.x = x; } else if (x < 0) { this.markB.frame.x = 0; } else { this.markB.frame.x = maxX; } this.markB.inspectionFrame.x = this.markB.frame.x - markPadding; requestAnimationFrame(() => this.draw()); } mouseMove(ev: MouseEvent) { let x = ev.offsetX - (offsetLeft || 0); let y = ev.offsetY - (offsetTop || 0); this.centerXPercentage = x / (this.frame.width || 0); if (this.centerXPercentage <= 0) { this.centerXPercentage = 0; } else if (this.centerXPercentage >= 1) { this.centerXPercentage = 1; } let maxX = this.frame.width || 0; if (this.markA.inspectionFrame.contains(x, y)) { this.markA.isHover = true; } else if (this.markB.inspectionFrame.contains(x, y)) { this.markB.isHover = true; } else { this.markA.isHover = false; this.markB.isHover = false; } if (this.movingMark) { let result = x - this.mouseDownOffsetX + this.mouseDownMovingMarkX; if (result >= 0 && result <= maxX) { this.movingMark.frame.x = result; } else if (result < 0) { this.movingMark.frame.x = 0; } else { this.movingMark.frame.x = maxX; } this.movingMark.inspectionFrame.x = this.movingMark.frame.x - markPadding; requestAnimationFrame(() => this.draw()); } if (this.isMovingRange && this.isMouseDown) { this.movingRange(maxX, x); } else if (this.isNewRange) { this.movingNewRange(maxX, x); } } mouseOut(ev: MouseEvent) { this.movingMark = null; } fillX() { if (this.range.startNS < 0) this.range.startNS = 0; if (this.range.endNS < 0) this.range.endNS = 0; if (this.range.endNS > this.range.totalNS) this.range.endNS = this.range.totalNS; if (this.range.startNS > this.range.totalNS) this.range.startNS = this.range.totalNS; this.range.startX = (this.range.startNS * (this.frame.width || 0)) / this.range.totalNS; this.range.endX = (this.range.endNS * (this.frame.width || 0)) / this.range.totalNS; this.markA.frame.x = this.range.startX; this.markA.inspectionFrame.x = this.markA.frame.x - markPadding; this.markB.frame.x = this.range.endX; this.markB.inspectionFrame.x = this.markB.frame.x - markPadding; } keyPressInW(): void { let animW = () => { if (this.scale === 50) return; this.range.startNS += (this.centerXPercentage * this.currentDuration * 2 * this.scale) / this.p; this.range.endNS -= ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale) / this.p; this.fillX(); this.draw(); this.pressFrameId = requestAnimationFrame(animW); }; this.pressFrameId = requestAnimationFrame(animW); } keyPressInS(): void { let animS = () => { if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return; this.range.startNS -= (this.centerXPercentage * this.currentDuration * 2 * this.scale) / this.p; this.range.endNS += ((1 - this.centerXPercentage) * this.currentDuration * 2 * this.scale) / this.p; this.fillX(); this.draw(); this.pressFrameId = requestAnimationFrame(animS); }; this.pressFrameId = requestAnimationFrame(animS); } keyPressInA(): void { let animA = () => { if (this.range.startNS == 0) return; let s = (this.scale / this.p) * this.currentDuration; this.range.startNS -= s; this.range.endNS -= s; this.fillX(); this.draw(); this.pressFrameId = requestAnimationFrame(animA); }; this.pressFrameId = requestAnimationFrame(animA); } keyPressInD(): void { let animD = () => { if (this.range.endNS >= this.range.totalNS) return; this.range.startNS += (this.scale / this.p) * this.currentDuration; this.range.endNS += (this.scale / this.p) * this.currentDuration; this.fillX(); this.draw(); this.pressFrameId = requestAnimationFrame(animD); }; this.pressFrameId = requestAnimationFrame(animD); } keyPress(ev: KeyboardEvent): void { if (this.animaStartTime === undefined) { this.animaStartTime = new Date().getTime(); } let startTime = new Date().getTime(); let duration = startTime - this.animaStartTime; if (duration < this.animTime) duration = this.animTime; this.currentDuration = duration; if (this.isPress) return; this.isPress = true; switch (ev.key.toLocaleLowerCase()) { case 'w': this.keyPressInW(); break; case 's': this.keyPressInS(); break; case 'a': this.keyPressInA(); break; case 'd': this.keyPressInD(); break; } } keyUpInW(startTime: number): void { let animW = () => { if (this.scale === 50) return; let dur = new Date().getTime() - startTime; this.range.startNS += (this.centerXPercentage * 100 * this.scale) / this.p; this.range.endNS -= ((1 - this.centerXPercentage) * 100 * this.scale) / this.p; this.fillX(); this.draw(); if (dur < 300) { requestAnimationFrame(animW); } }; requestAnimationFrame(animW); } keyUpInS(startTime: number): void { let animS = () => { if (this.range.startNS <= 0 && this.range.endNS >= this.range.totalNS) return; let dur = new Date().getTime() - startTime; this.range.startNS -= (this.centerXPercentage * 100 * this.scale) / this.p; this.range.endNS += ((1 - this.centerXPercentage) * 100 * this.scale) / this.p; this.fillX(); this.draw(); if (dur < 300) { requestAnimationFrame(animS); } }; requestAnimationFrame(animS); } keyUpInA(startTime: number): void { let animA = () => { if (this.range.startNS <= 0) return; let dur = new Date().getTime() - startTime; let s = (this.scale * 80) / this.p; this.range.startNS -= s; this.range.endNS -= s; this.fillX(); this.draw(); if (dur < 300) { requestAnimationFrame(animA); } }; animA(); } keyUpInD(startTime: number): void { let animD = () => { if (this.range.endNS >= this.range.totalNS) return; let dur = new Date().getTime() - startTime; let s = (this.scale * 80) / this.p; this.range.startNS += s; this.range.endNS += s; this.fillX(); this.draw(); if (dur < 300) { requestAnimationFrame(animD); } }; animD(); } keyUp(ev: KeyboardEvent): void { this.animaStartTime = undefined; this.isPress = false; if (this.pressFrameId != -1) { cancelAnimationFrame(this.pressFrameId); } let startTime = new Date().getTime(); switch (ev.key) { case 'w': this.keyUpInW.call(this, startTime); break; case 's': this.keyUpInS.call(this, startTime); break; case 'a': this.keyUpInA.call(this, startTime); break; case 'd': this.keyUpInD.call(this, startTime); break; } } } export class Flag { x: number = 0; y: number = 0; width: number = 0; height: number = 0; time: number = 0; color: string = ''; selected: boolean = false; text: string = ''; constructor( x: number, y: number, width: number, height: number, time: number, color: string = '#999999', selected = false ) { this.x = x; this.y = y; this.width = width; this.height = height; this.time = time; this.color = color; this.selected = selected; } }