| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import './icons.html.js'; |
| import './support_tool_shared.css.js'; |
| import 'chrome://resources/cr_elements/cr_button/cr_button.js'; |
| import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js'; |
| |
| import type {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js'; |
| import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import type {BrowserProxy} from './browser_proxy.js'; |
| import {BrowserProxyImpl} from './browser_proxy.js'; |
| import {getTemplate} from './screenshot.html.js'; |
| import {SupportToolPageMixin} from './support_tool_page_mixin.js'; |
| |
| const ScreenshotElementBase = SupportToolPageMixin(PolymerElement); |
| |
| export class ScreenshotElement extends ScreenshotElementBase { |
| static get is() { |
| return 'screenshot-element'; |
| } |
| |
| static get template() { |
| return getTemplate(); |
| } |
| |
| static get properties() { |
| return { |
| hasScreenshotPreview_: { |
| type: Boolean, |
| value: false, |
| }, |
| screenshotBase64_: { |
| type: String, |
| value: '', |
| }, |
| originalScreenshotBase64_: { |
| type: String, |
| value: '', |
| }, |
| showEditor_: { |
| type: Boolean, |
| value: false, |
| }, |
| showDeleteButton_: { |
| type: Boolean, |
| value: false, |
| }, |
| buttonX_: { |
| type: Number, |
| value: 0, |
| }, |
| buttonY_: { |
| type: Number, |
| value: 0, |
| }, |
| }; |
| } |
| |
| declare private hasScreenshotPreview_: boolean; |
| declare private screenshotBase64_: string; |
| declare private originalScreenshotBase64_: string; |
| declare private showEditor_: boolean; |
| // The coordinate of the top left corner of the canvas. |
| private canvasX_: number = 0; |
| private canvasY_: number = 0; |
| private context_: CanvasRenderingContext2D; |
| // The coordinates that define user-selected regions. The four numbers are: |
| // the x and y coordinates of the top left corner, and the width and height |
| // of the rectangle. |
| private rects_: Set<[number, number, number, number]> = new Set(); |
| // Store the rectangles that remain when the user clicks the confirm button. |
| private confirmedRects_: Set<[number, number, number, number]> = new Set(); |
| // The coordinates of the cursor when the user starts to hold the mouse down. |
| private cornerX_: number = 0; |
| private cornerY_: number = 0; |
| private mouseDown_: boolean = false; |
| declare private showDeleteButton_: boolean; |
| // The coordinates that define the rectangle to be deleted. The four numbers |
| // are in accordance with `rects_`. |
| private selectedRect_: [number, number, number, number]; |
| // The coordinates which the delete button should be located at. |
| declare private buttonX_: number; |
| declare private buttonY_: number; |
| private browserProxy_: BrowserProxy = BrowserProxyImpl.getInstance(); |
| |
| setScreenshotData(dataBase64: string) { |
| this.screenshotBase64_ = dataBase64; |
| this.originalScreenshotBase64_ = dataBase64; |
| this.hasScreenshotPreview_ = true; |
| } |
| |
| getEditedScreenshotBase64(): string { |
| return this.screenshotBase64_; |
| } |
| |
| getOriginalScreenshotBase64(): string { |
| return this.originalScreenshotBase64_; |
| } |
| |
| private onTakeScreenshotClick_() { |
| this.browserProxy_.takeScreenshot(); |
| } |
| |
| private onRemoveScreenshotClick_() { |
| this.hasScreenshotPreview_ = false; |
| this.rects_.clear(); |
| this.confirmedRects_.clear(); |
| this.screenshotBase64_ = ''; |
| this.originalScreenshotBase64_ = ''; |
| } |
| |
| private onEditScreenshotClick_() { |
| // Make a copy of the confirmed rectangles. We will only work with this |
| // copy when the user is actively drawing. |
| this.rects_ = new Set(this.confirmedRects_); |
| this.showEditor_ = true; |
| } |
| |
| private onOpenDialog_() { |
| const canvas = this.$$<HTMLCanvasElement>('#screenshotCanvas')!; |
| const image = this.$$<HTMLImageElement>('#screenshotEditorBG')!; |
| canvas.height = image.height; |
| canvas.width = image.width; |
| this.canvasX_ = canvas.getBoundingClientRect().left; |
| this.canvasY_ = canvas.getBoundingClientRect().top; |
| this.context_ = canvas.getContext('2d')!; |
| this.context_.clearRect(0, 0, canvas.width, canvas.height); |
| this.context_.fill(); |
| |
| const dialog = this.$$<CrDialogElement>('#editor')!; |
| const body = dialog.shadowRoot.querySelector<HTMLElement>('#container')!; |
| // Make sure that the canvas coordinates are always up-to-date. |
| window.addEventListener('scroll', this.onCoordsChanged_.bind(this)); |
| window.addEventListener('resize', this.onCoordsChanged_.bind(this)); |
| body.addEventListener('scroll', this.onCoordsChanged_.bind(this)); |
| |
| // Draw rectangles based on mouse activities. |
| canvas.addEventListener('mousedown', this.onCanvasMouseDown_.bind(this)); |
| canvas.addEventListener('mousemove', this.onCanvasMouseMove_.bind(this)); |
| canvas.addEventListener('mouseup', this.onCanvasMouseUp_.bind(this)); |
| } |
| |
| private onCoordsChanged_() { |
| this.canvasX_ = this.context_.canvas.getBoundingClientRect().left; |
| this.canvasY_ = this.context_.canvas.getBoundingClientRect().top; |
| } |
| |
| private onCanvasMouseDown_(event: MouseEvent) { |
| if (!this.mouseDown_) { |
| this.cornerX_ = event.clientX - this.canvasX_; |
| this.cornerY_ = event.clientY - this.canvasY_; |
| this.mouseDown_ = true; |
| } |
| } |
| |
| private onCanvasMouseMove_(event: MouseEvent) { |
| const mouseX = event.clientX - this.canvasX_; |
| const mouseY = event.clientY - this.canvasY_; |
| this.context_.clearRect( |
| 0, 0, this.context_.canvas.width, this.context_.canvas.height); |
| this.context_.beginPath(); |
| this.showDeleteButton_ = false; |
| // Re-draw all the rectangles. |
| for (const rect of this.rects_) { |
| this.context_.rect(rect[0], rect[1], rect[2], rect[3]); |
| if (!this.mouseDown_ && !this.showDeleteButton_ && |
| this.context_.isPointInPath(mouseX, mouseY)) { |
| // If we are not drawing new rectangles, show the remove button when the |
| // cursor is on the top of a rectangle. |
| this.showDeleteButton_ = true; |
| this.selectedRect_ = rect; |
| this.buttonX_ = rect[0] + rect[2]; |
| this.buttonY_ = rect[1] - 24; |
| } |
| } |
| if (this.mouseDown_) { |
| // Draw the rectangle according to the cursor position. |
| this.context_.rect( |
| this.cornerX_, this.cornerY_, mouseX - this.cornerX_, |
| mouseY - this.cornerY_); |
| } |
| this.context_.fill(); |
| } |
| |
| private onCanvasMouseUp_(event: MouseEvent) { |
| const mouseX = event.clientX - this.canvasX_; |
| const mouseY = event.clientY - this.canvasY_; |
| // Locate the top left corner. |
| this.rects_.add([ |
| Math.min(this.cornerX_, mouseX), |
| Math.min(this.cornerY_, mouseY), |
| Math.abs(mouseX - this.cornerX_), |
| Math.abs(mouseY - this.cornerY_), |
| ]); |
| this.mouseDown_ = false; |
| } |
| |
| private onClickDeleteRect_() { |
| this.rects_.delete(this.selectedRect_); |
| this.showDeleteButton_ = false; |
| this.context_.clearRect( |
| 0, 0, this.context_.canvas.width, this.context_.canvas.height); |
| // Re-draw the remaining rectangles. |
| this.context_.beginPath(); |
| for (const rect of this.rects_) { |
| this.context_.rect(rect[0], rect[1], rect[2], rect[3]); |
| } |
| this.context_.fill(); |
| } |
| |
| private onClickConfirm_() { |
| this.confirmedRects_ = this.rects_; |
| const background = this.$$<HTMLImageElement>('#screenshotEditorBG')!; |
| this.context_.drawImage(background, 0, 0); |
| // Draw the rectangles. |
| this.context_.fill(); |
| this.screenshotBase64_ = this.context_.canvas.toDataURL('image/jpeg', 0.9); |
| this.showEditor_ = false; |
| } |
| |
| private onCloseDialog_() { |
| this.showEditor_ = false; |
| } |
| |
| private onClickCancel_() { |
| this.showEditor_ = false; |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'screenshot-element': ScreenshotElement; |
| } |
| } |
| |
| customElements.define(ScreenshotElement.is, ScreenshotElement); |