diff --git a/ash/webui/camera_app_ui/resources/js/js.gni b/ash/webui/camera_app_ui/resources/js/js.gni index 281c4a5e4d16a..8f2c5a87bac1b 100644 --- a/ash/webui/camera_app_ui/resources/js/js.gni +++ b/ash/webui/camera_app_ui/resources/js/js.gni @@ -63,13 +63,13 @@ compile_js_files = [ "timer.js", "toast.js", "tooltip.js", - "type.js", + "type.ts", "unload.js", "untrusted_ga_helper.js", "untrusted_helper_interfaces.js", "untrusted_script_loader.js", "untrusted_video_processor_helper.js", - "util.js", + "util.ts", "views/camera_intent.js", "views/camera.js", "views/camera/layout.js", diff --git a/ash/webui/camera_app_ui/resources/js/main.js b/ash/webui/camera_app_ui/resources/js/main.js index dd2b9e599b9e7..a21fd70202ead 100644 --- a/ash/webui/camera_app_ui/resources/js/main.js +++ b/ash/webui/camera_app_ui/resources/js/main.js @@ -40,6 +40,7 @@ import { } from './type.js'; import {addUnloadCallback} from './unload.js'; import * as util from './util.js'; +import {checkEnumVariant} from './util.js'; import {Camera} from './views/camera.js'; import {CameraIntent} from './views/camera_intent.js'; import {Dialog} from './views/dialog.js'; @@ -396,27 +397,9 @@ function parseSearchParams() { const url = new URL(window.location.href); const params = url.searchParams; - // TODO(pihsun): Intent.create has almost same code for checking a string is - // an enum variant, extract them to a util function when we change TypeScript - // since the util function type is hard to be described in closure compiler - // due to lack of generic type bounds. - /** @type {?Facing} */ - const facing = (() => { - const facing = params.get('facing'); - if (facing === null || !Object.values(Facing).includes(facing)) { - return null; - } - return /** @type {!Facing} */ (facing); - })(); + const facing = checkEnumVariant(Facing, params.get('facing')); - /** @type {?Mode} */ - const mode = (() => { - const mode = params.get('mode'); - if (mode === null || !Object.values(Mode).includes(mode)) { - return null; - } - return /** @type {!Mode} */ (mode); - })(); + const mode = checkEnumVariant(Mode, params.get('mode')); /** @type {?Intent} */ const intent = (() => { @@ -486,28 +469,33 @@ let instance = null; appWindow.reportPerf({event, duration, perfInfo}); } }); - const states = Object.values(PerfEvent); - states.push(state.State.TAKING); - states.forEach((s) => { - state.addObserver(s, (val, extras) => { - let event = s; - if (s === state.State.TAKING) { - // 'taking' state indicates either taking photo or video. Skips for - // video-taking case since we only want to collect the metrics of - // photo-taking. - if (state.get(Mode.VIDEO)) { - return; - } - event = PerfEvent.PHOTO_TAKING; - } + state.addObserver(state.State.TAKING, (val, extras) => { + // 'taking' state indicates either taking photo or video. Skips for + // video-taking case since we only want to collect the metrics of + // photo-taking. + if (state.get(Mode.VIDEO)) { + return; + } + const event = PerfEvent.PHOTO_TAKING; + + if (val) { + perfLogger.start(event); + } else { + perfLogger.stop(event, extras); + } + }); + + const states = Object.values(PerfEvent); + for (const event of states) { + state.addObserver(event, (val, extras) => { if (val) { perfLogger.start(event); } else { perfLogger.stop(event, extras); } }); - }); + } instance = new App({perfLogger, intent, facing, mode}); await instance.start( diff --git a/ash/webui/camera_app_ui/resources/js/mojo/device_operator.js b/ash/webui/camera_app_ui/resources/js/mojo/device_operator.js index 51c2f160e754a..91a4578c7f92a 100644 --- a/ash/webui/camera_app_ui/resources/js/mojo/device_operator.js +++ b/ash/webui/camera_app_ui/resources/js/mojo/device_operator.js @@ -681,10 +681,7 @@ export class DeviceOperator { /** * Creates a new instance of DeviceOperator if it is not set. Returns the * exist instance. - * TODO(b/172340451): Use force casting rather than template for the type - * checking of Proxy after switching to TypeScript. - * @return {!Promise} The singleton instance. - * @template T + * @return {!Promise} The singleton instance. */ static async getInstance() { await readyEvent.wait(); @@ -705,7 +702,7 @@ export class DeviceOperator { return target[property]; }, }; - return /** @type {!T} */ (new Proxy(instance, deviceOperatorWrapper)); + return new Proxy(instance, deviceOperatorWrapper); } /** diff --git a/ash/webui/camera_app_ui/resources/js/type.js b/ash/webui/camera_app_ui/resources/js/type.js deleted file mode 100644 index 74019be7a5c80..0000000000000 --- a/ash/webui/camera_app_ui/resources/js/type.js +++ /dev/null @@ -1,394 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * Photo or video resolution. - */ -export class Resolution { - /** - * @param {number} width - * @param {number} height - */ - constructor(width, height) { - /** - * @type {number} - * @const - */ - this.width = width; - - /** - * @type {number} - * @const - */ - this.height = height; - } - - /** - * @return {number} Total pixel number. - */ - get area() { - return this.width * this.height; - } - - /** - * Aspect ratio calculates from width divided by height. - * @return {number} - */ - get aspectRatio() { - // Approxitate to 4 decimal places to prevent precision error during - // comparing. - return parseFloat((this.width / this.height).toFixed(4)); - } - - /** - * Compares width/height of resolutions, see if they are equal or not. - * @param {!Resolution} resolution Resolution to be compared with. - * @return {boolean} Whether width/height of resolutions are equal. - */ - equals(resolution) { - return this.width === resolution.width && this.height === resolution.height; - } - - /** - * Compares aspect ratio of resolutions, see if they are equal or not. - * @param {!Resolution} resolution Resolution to be compared with. - * @return {boolean} Whether aspect ratio of resolutions are equal. - */ - aspectRatioEquals(resolution) { - return this.width * resolution.height === this.height * resolution.width; - } - - /** - * Create Resolution object from string. - * @param {string} s - * @return {!Resolution} - */ - static fromString(s) { - const [width, height] = s.split('x').map((x) => Number(x)); - return new Resolution(width, height); - } - - /** - * @override - */ - toString() { - return `${this.width}x${this.height}`; - } -} - -/** - * Types of common mime types. - * @enum {string} - */ -export const MimeType = { - GIF: 'image/gif', - JPEG: 'image/jpeg', - MP4: 'video/mp4', - PDF: 'application/pdf', -}; - -/** - * Capture modes. - * @enum {string} - */ -export const Mode = { - PHOTO: 'photo', - VIDEO: 'video', - SQUARE: 'square', - PORTRAIT: 'portrait', - SCAN: 'scan', -}; - -/** - * Camera facings. - * @enum {string} - */ -export const Facing = { - USER: 'user', - ENVIRONMENT: 'environment', - EXTERNAL: 'external', - // VIRTUAL_{facing} is for labeling video device for configuring extra stream - // from corresponding {facing} video device. - VIRTUAL_USER: 'virtual_user', - VIRTUAL_ENV: 'virtual_environment', - VIRTUAL_EXT: 'virtual_external', - NOT_SET: '(not set)', -}; - -/** - * @enum {string} - */ -export const ViewName = { - CAMERA: 'view-camera', - CROP_DOCUMENT: 'view-crop-document', - DOCUMENT_MODE_DIALOG: 'view-document-mode-dialog', - EXPERT_SETTINGS: 'view-expert-settings', - FLASH: 'view-flash', - GRID_SETTINGS: 'view-grid-settings', - MESSAGE_DIALOG: 'view-message-dialog', - PHOTO_RESOLUTION_SETTINGS: 'view-photo-resolution-settings', - PTZ_PANEL: 'view-ptz-panel', - RESOLUTION_SETTINGS: 'view-resolution-settings', - REVIEW: 'view-review', - SETTINGS: 'view-settings', - SPLASH: 'view-splash', - TIMER_SETTINGS: 'view-timer-settings', - VIDEO_RESOLUTION_SETTINGS: 'view-video-resolution-settings', - WARNING: 'view-warning', -}; - -/** - * @enum {string} - */ -export const VideoType = { - MP4: 'mp4', - GIF: 'gif', -}; - -/** - * @enum {number} - */ -export const Rotation = { - ANGLE_0: 0, - ANGLE_90: 90, - ANGLE_180: 180, - ANGLE_270: 270, -}; - - -// The types here are used only in jsdoc and are required to be explicitly -// exported in order to be referenced by closure compiler. -// TODO(inker): Exports/Imports these jsdoc only types by closure compiler -// comment syntax. The implementation of syntax is tracked here: -// https://github.com/google/closure-compiler/issues/3041 - -/** - * @typedef {{ - * width: number, - * height: number, - * maxFps: number, - * }} - */ -export let VideoConfig; - -/** - * @typedef {{ - * minFps: number, - * maxFps: number, - * }} - */ -export let FpsRange; - -/** - * A list of resolutions. - * @typedef {!Array} - */ -export let ResolutionList; - -/** - * Map of all available resolution to its maximal supported capture fps. The key - * of the map is the resolution and the corresponding value is the maximal - * capture fps under that resolution. - * @typedef {!Object<(!Resolution|string), number>} - */ -export let MaxFpsInfo; - -/** - * List of supported capture fps ranges. - * @typedef {!Array} - */ -export let FpsRangeList; - -/** - * Type for performance event. - * @enum {string} - */ -export const PerfEvent = { - CAMERA_SWITCHING: 'camera-switching', - GIF_CAPTURE_POST_PROCESSING: 'gif-capture-post-processing', - LAUNCHING_FROM_LAUNCH_APP_COLD: 'launching-from-launch-app-cold', - LAUNCHING_FROM_LAUNCH_APP_WARM: 'launching-from-launch-app-warm', - LAUNCHING_FROM_WINDOW_CREATION: 'launching-from-window-creation', - MODE_SWITCHING: 'mode-switching', - PHOTO_CAPTURE_POST_PROCESSING: 'photo-capture-post-processing', - PHOTO_CAPTURE_SHUTTER: 'photo-capture-shutter', - PHOTO_TAKING: 'photo-taking', - PORTRAIT_MODE_CAPTURE_POST_PROCESSING: - 'portrait-mode-capture-post-processing', - VIDEO_CAPTURE_POST_PROCESSING: 'video-capture-post-processing', -}; - -/** - * @typedef {{ - * blob: !Blob, - * resolution: !Resolution, - * }} - */ -export let ImageBlob; - -/** - * @typedef {{ - * hasError?: boolean, - * resolution?: !Resolution, - * facing?: !Facing, - * }} - */ -export let PerfInformation; - -/** - * @typedef {{ - * event: !PerfEvent, - * duration: number, - * perfInfo?: !PerfInformation, - * }} - */ -export let PerfEntry; - -/** - * Error reported in testing run. - * @typedef {{ - * type: !ErrorType, - * level: !ErrorLevel, - * stack: string, - * time: number, - * name: string, - * }} - */ -export let ErrorInfo; - -/** - * Types of error used in ERROR metrics. - * @enum {string} - */ -export const ErrorType = { - BROKEN_THUMBNAIL: 'broken-thumbnail', - DEVICE_INFO_UPDATE_FAILURE: 'device-info-update-failure', - DEVICE_NOT_EXIST: 'device-not-exist', - EMPTY_FILE: 'empty-file', - FILE_SYSTEM_FAILURE: 'file-system-failure', - FRAME_ROTATION_NOT_DISABLED: 'frame-rotation-not-disabled', - HANDLE_CAMERA_RESULT_FAILURE: 'handle-camera-result-failure', - IDLE_DETECTOR_FAILURE: 'idle-detector-failure', - INVALID_REVIEW_UI_STATE: 'invalid-review-ui-state', - METADATA_MAPPING_FAILURE: 'metadata-mapping-failure', - MULTIPLE_STREAMS_FAILURE: 'multiple-streams-failure', - NO_AVAILABLE_LEVEL: 'no-available-level', - PERF_METRICS_FAILURE: 'perf-metrics-failure', - PRELOAD_IMAGE_FAILURE: 'preload-image-failure', - SET_FPS_RANGE_FAILURE: 'set-fps-range-failure', - START_CAMERA_FAILURE: 'start-camera-failure', - START_CAPTURE_FAILURE: 'start-capture-failure', - STOP_CAPTURE_FAILURE: 'stop-capture-failure', - UNCAUGHT_PROMISE: 'uncaught-promise', - UNKNOWN_FACING: 'unknown-facing', - UNSAFE_INTEGER: 'unsafe-integer', - UNSUPPORTED_PROTOCOL: 'unsupported-protocol', -}; - -/** - * Error level used in ERROR metrics. - * @enum {string} - */ -export const ErrorLevel = { - WARNING: 'WARNING', - ERROR: 'ERROR', -}; - -/** - * Throws when a method is not implemented. - */ -export class NotImplementedError extends Error { - /** - * @param {string=} message - * @public - */ - constructor(message = 'Method is not implemented') { - super(message); - this.name = this.constructor.name; - } -} - -/** - * Throws when an action is canceled. - */ -export class CanceledError extends Error { - /** - * @param {string=} message - * @public - */ - constructor(message = 'The action is canceled') { - super(message); - this.name = this.constructor.name; - } -} - -/** - * Throws when an element fails to load a source. - */ -export class LoadError extends Error { - /** - * @param {string=} message - * @public - */ - constructor(message = 'Source failed to load') { - super(message); - this.name = this.constructor.name; - } -} - -/** - * Throws when an media element fails to play. - */ -export class PlayError extends Error { - /** - * @param {string=} message - * @public - */ - constructor(message = 'Media element failed to play') { - super(message); - this.name = this.constructor.name; - } -} - -/** - * Throws when an media element play a malformed file. - */ -export class PlayMalformedError extends Error { - /** - * @param {string=} message - * @public - */ - constructor(message = 'Media element failed to play a malformed file') { - super(message); - this.name = this.constructor.name; - } -} - -/** - * Throws when the data to generate thumbnail is totally empty. - */ -export class EmptyThumbnailError extends Error { - /** - * @param {string=} message - * @public - */ - constructor(message = 'The thumbnail is empty') { - super(message); - this.name = this.constructor.name; - } -} - -/** - * Throws when the recording is ended with no chunk returned. - */ -export class NoChunkError extends Error { - /** - * @param {string=} message - * @public - */ - constructor(message = 'No chunk is received during recording session') { - super(message); - this.name = this.constructor.name; - } -} diff --git a/ash/webui/camera_app_ui/resources/js/type.ts b/ash/webui/camera_app_ui/resources/js/type.ts new file mode 100644 index 0000000000000..c6977ff00a800 --- /dev/null +++ b/ash/webui/camera_app_ui/resources/js/type.ts @@ -0,0 +1,303 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Photo or video resolution. + */ +export class Resolution { + constructor(readonly width: number, readonly height: number) {} + + /** + * @return Total pixel number. + */ + get area(): number { + return this.width * this.height; + } + + /** + * Aspect ratio calculates from width divided by height. + */ + get aspectRatio(): number { + // Approximate to 4 decimal places to prevent precision error during + // comparing. + return parseFloat((this.width / this.height).toFixed(4)); + } + + /** + * Compares width/height of resolutions, see if they are equal or not. + * @param resolution Resolution to be compared with. + * @return Whether width/height of resolutions are equal. + */ + equals(resolution: Resolution): boolean { + return this.width === resolution.width && this.height === resolution.height; + } + + /** + * Compares aspect ratio of resolutions, see if they are equal or not. + * @param resolution Resolution to be compared with. + * @return Whether aspect ratio of resolutions are equal. + */ + aspectRatioEquals(resolution: Resolution): boolean { + return this.width * resolution.height === this.height * resolution.width; + } + + /** + * Create Resolution object from string. + */ + static fromString(s: string): Resolution { + const [width, height] = s.split('x').map((x) => Number(x)); + return new Resolution(width, height); + } + + toString(): string { + return `${this.width}x${this.height}`; + } +} + +/** + * Types of common mime types. + */ +export enum MimeType { + GIF = 'image/gif', + JPEG = 'image/jpeg', + MP4 = 'video/mp4', + PDF = 'application/pdf', +} + +/** + * Capture modes. + */ +export enum Mode { + PHOTO = 'photo', + VIDEO = 'video', + SQUARE = 'square', + PORTRAIT = 'portrait', + SCAN = 'scan', +} + +/** + * Camera facings. + */ +export enum Facing { + USER = 'user', + ENVIRONMENT = 'environment', + EXTERNAL = 'external', + // VIRTUAL_{facing} is for labeling video device for configuring extra stream + // from corresponding {facing} video device. + VIRTUAL_USER = 'virtual_user', + VIRTUAL_ENV = 'virtual_environment', + VIRTUAL_EXT = 'virtual_external', + NOT_SET = '(not set)', +} + +export enum ViewName { + CAMERA = 'view-camera', + CROP_DOCUMENT = 'view-crop-document', + DOCUMENT_MODE_DIALOG = 'view-document-mode-dialog', + EXPERT_SETTINGS = 'view-expert-settings', + FLASH = 'view-flash', + GRID_SETTINGS = 'view-grid-settings', + MESSAGE_DIALOG = 'view-message-dialog', + PHOTO_RESOLUTION_SETTINGS = 'view-photo-resolution-settings', + PTZ_PANEL = 'view-ptz-panel', + RESOLUTION_SETTINGS = 'view-resolution-settings', + REVIEW = 'view-review', + SETTINGS = 'view-settings', + SPLASH = 'view-splash', + TIMER_SETTINGS = 'view-timer-settings', + VIDEO_RESOLUTION_SETTINGS = 'view-video-resolution-settings', + WARNING = 'view-warning', +} + +export enum VideoType { + MP4 = 'mp4', + GIF = 'gif', +} + +export enum Rotation { + ANGLE_0 = 0, + ANGLE_90 = 90, + ANGLE_180 = 180, + ANGLE_270 = 270, +} + +export interface VideoConfig { + width: number; + height: number; + maxFps: number; +} + +export interface FpsRange { + minFps: number; + maxFps: number; +} + +/** + * A list of resolutions. + */ +export type ResolutionList = Resolution[]; + +/** + * Map of all available resolution to its maximal supported capture fps. The key + * of the map is the resolution and the corresponding value is the maximal + * capture fps under that resolution. + */ +export type MaxFpsInfo = Record; + +/** + * List of supported capture fps ranges. + */ +export type FpsRangeList = FpsRange[]; + +/** + * Type for performance event. + */ +export enum PerfEvent { + CAMERA_SWITCHING = 'camera-switching', + GIF_CAPTURE_POST_PROCESSING = 'gif-capture-post-processing', + LAUNCHING_FROM_LAUNCH_APP_COLD = 'launching-from-launch-app-cold', + LAUNCHING_FROM_LAUNCH_APP_WARM = 'launching-from-launch-app-warm', + LAUNCHING_FROM_WINDOW_CREATION = 'launching-from-window-creation', + MODE_SWITCHING = 'mode-switching', + PHOTO_CAPTURE_POST_PROCESSING = 'photo-capture-post-processing', + PHOTO_CAPTURE_SHUTTER = 'photo-capture-shutter', + PHOTO_TAKING = 'photo-taking', + PORTRAIT_MODE_CAPTURE_POST_PROCESSING = + 'portrait-mode-capture-post-processing', + VIDEO_CAPTURE_POST_PROCESSING = 'video-capture-post-processing', +} + +export interface ImageBlob { + blob: Blob; + resolution: Resolution; +} + +export interface PerfInformation { + hasError?: boolean; + resolution?: Resolution; + facing?: Facing; +} + +export interface PerfEntry { + event: PerfEvent; + duration: number; + perfInfo?: PerfInformation; +} + +/** + * Error reported in testing run. + */ +export interface ErrorInfo { + type: ErrorType; + level: ErrorLevel; + stack: string; + time: number; + name: string; +} + +/** + * Types of error used in ERROR metrics. + */ +export enum ErrorType { + BROKEN_THUMBNAIL = 'broken-thumbnail', + DEVICE_INFO_UPDATE_FAILURE = 'device-info-update-failure', + DEVICE_NOT_EXIST = 'device-not-exist', + EMPTY_FILE = 'empty-file', + FILE_SYSTEM_FAILURE = 'file-system-failure', + FRAME_ROTATION_NOT_DISABLED = 'frame-rotation-not-disabled', + HANDLE_CAMERA_RESULT_FAILURE = 'handle-camera-result-failure', + IDLE_DETECTOR_FAILURE = 'idle-detector-failure', + INVALID_REVIEW_UI_STATE = 'invalid-review-ui-state', + METADATA_MAPPING_FAILURE = 'metadata-mapping-failure', + MULTIPLE_STREAMS_FAILURE = 'multiple-streams-failure', + NO_AVAILABLE_LEVEL = 'no-available-level', + PERF_METRICS_FAILURE = 'perf-metrics-failure', + PRELOAD_IMAGE_FAILURE = 'preload-image-failure', + SET_FPS_RANGE_FAILURE = 'set-fps-range-failure', + START_CAMERA_FAILURE = 'start-camera-failure', + START_CAPTURE_FAILURE = 'start-capture-failure', + STOP_CAPTURE_FAILURE = 'stop-capture-failure', + UNCAUGHT_PROMISE = 'uncaught-promise', + UNKNOWN_FACING = 'unknown-facing', + UNSAFE_INTEGER = 'unsafe-integer', + UNSUPPORTED_PROTOCOL = 'unsupported-protocol', +} + +/** + * Error level used in ERROR metrics. + */ +export enum ErrorLevel { + WARNING = 'WARNING', + ERROR = 'ERROR', +} + +/** + * Throws when a method is not implemented. + */ +export class NotImplementedError extends Error { + constructor(message = 'Method is not implemented') { + super(message); + this.name = this.constructor.name; + } +} + +/** + * Throws when an action is canceled. + */ +export class CanceledError extends Error { + constructor(message = 'The action is canceled') { + super(message); + this.name = this.constructor.name; + } +} + +/** + * Throws when an element fails to load a source. + */ +export class LoadError extends Error { + constructor(message = 'Source failed to load') { + super(message); + this.name = this.constructor.name; + } +} + +/** + * Throws when an media element fails to play. + */ +export class PlayError extends Error { + constructor(message = 'Media element failed to play') { + super(message); + this.name = this.constructor.name; + } +} + +/** + * Throws when an media element play a malformed file. + */ +export class PlayMalformedError extends Error { + constructor(message = 'Media element failed to play a malformed file') { + super(message); + this.name = this.constructor.name; + } +} + +/** + * Throws when the data to generate thumbnail is totally empty. + */ +export class EmptyThumbnailError extends Error { + constructor(message = 'The thumbnail is empty') { + super(message); + this.name = this.constructor.name; + } +} + +/** + * Throws when the recording is ended with no chunk returned. + */ +export class NoChunkError extends Error { + constructor(message = 'No chunk is received during recording session') { + super(message); + this.name = this.constructor.name; + } +} diff --git a/ash/webui/camera_app_ui/resources/js/util.js b/ash/webui/camera_app_ui/resources/js/util.ts similarity index 73% rename from ash/webui/camera_app_ui/resources/js/util.js rename to ash/webui/camera_app_ui/resources/js/util.ts index 9d74527897ed3..9c1401b5b73f1 100644 --- a/ash/webui/camera_app_ui/resources/js/util.js +++ b/ash/webui/camera_app_ui/resources/js/util.ts @@ -5,24 +5,22 @@ import * as animate from './animation.js'; import {assertInstanceof} from './assert.js'; import * as dom from './dom.js'; -// eslint-disable-next-line no-unused-vars import {I18nString} from './i18n_string.js'; import * as Comlink from './lib/comlink.js'; import * as loadTimeData from './models/load_time_data.js'; import * as state from './state.js'; import * as tooltip from './tooltip.js'; -import { - Facing, -} from './type.js'; +import {Facing} from './type.js'; import {WaitableEvent} from './waitable_event.js'; /** * Creates a canvas element for 2D drawing. - * @param {{width: number, height: number}} params Width/Height of the canvas. - * @return {{canvas: !HTMLCanvasElement, ctx: !CanvasRenderingContext2D}} - * Returns canvas element and the context for 2D drawing. + * @param params Width/Height of the canvas. + * @return Returns canvas element and the context for 2D drawing. */ -export function newDrawingCanvas({width, height}) { +export function newDrawingCanvas( + {width, height}: {width: number, height: number}): + {canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D} { const canvas = dom.create('canvas', HTMLCanvasElement); canvas.width = width; canvas.height = height; @@ -31,11 +29,7 @@ export function newDrawingCanvas({width, height}) { return {canvas, ctx}; } -/** - * @param {!ImageBitmap} bitmap - * @return {!Promise} - */ -export function bitmapToJpegBlob(bitmap) { +export function bitmapToJpegBlob(bitmap: ImageBitmap): Promise { const {canvas, ctx} = newDrawingCanvas({width: bitmap.width, height: bitmap.height}); ctx.drawImage(bitmap, 0, 0); @@ -52,10 +46,10 @@ export function bitmapToJpegBlob(bitmap) { /** * Returns a shortcut string, such as Ctrl-Alt-A. - * @param {!KeyboardEvent} event Keyboard event. - * @return {string} Shortcut identifier. + * @param event Keyboard event. + * @return Shortcut identifier. */ -export function getShortcutIdentifier(event) { +export function getShortcutIdentifier(event: KeyboardEvent): string { let identifier = (event.ctrlKey ? 'Ctrl-' : '') + (event.altKey ? 'Alt-' : '') + (event.shiftKey ? 'Shift-' : '') + (event.metaKey ? 'Meta-' : ''); @@ -90,17 +84,16 @@ export function getShortcutIdentifier(event) { /** * Opens help. */ -export function openHelp() { +export function openHelp(): void { window.open( 'https://support.google.com/chromebook/?p=camera_usage_on_chromebook'); } /** * Sets up i18n messages on DOM subtree by i18n attributes. - * @param {!Element|!DocumentFragment} rootElement Root of DOM subtree to be set - * up with. + * @param rootElement Root of DOM subtree to be set up with. */ -export function setupI18nElements(rootElement) { +export function setupI18nElements(rootElement: Element|DocumentFragment): void { const getElements = (attr) => dom.getAllFrom(rootElement, '[' + attr + ']', HTMLElement); const getMessage = (element, attr) => @@ -127,11 +120,8 @@ export function setupI18nElements(rootElement) { /** * Reads blob into Image. - * @param {!Blob} blob - * @return {!Promise} - * @throws {!Error} */ -export function blobToImage(blob) { +export function blobToImage(blob: Blob): Promise { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); @@ -142,29 +132,30 @@ export function blobToImage(blob) { /** * Gets default facing according to device mode. - * @return {!Facing} */ -export function getDefaultFacing() { +export function getDefaultFacing(): Facing { return state.get(state.State.TABLET) ? Facing.ENVIRONMENT : Facing.USER; } /** * Toggle checked value of element. - * @param {!HTMLInputElement} element - * @param {boolean} checked */ -export function toggleChecked(element, checked) { +export function toggleChecked( + element: HTMLInputElement, checked: boolean): void { element.checked = checked; element.dispatchEvent(new Event('change')); } /** * Binds on/off of specified state with different aria label on an element. - * @param {{element: !Element, state: !state.State, onLabel: !I18nString, - * offLabel: !I18nString}} params */ export function bindElementAriaLabelWithState( - {element, state: s, onLabel, offLabel}) { + {element, state: s, onLabel, offLabel}: { + element: Element, + state: state.State, + onLabel: I18nString, + offLabel: I18nString, + }): void { const update = (value) => { const label = value ? onLabel : offLabel; element.setAttribute('i18n-label', label); @@ -176,9 +167,8 @@ export function bindElementAriaLabelWithState( /** * Sets inkdrop effect on button or label in setting menu. - * @param {!HTMLElement} el */ -export function setInkdropEffect(el) { +export function setInkdropEffect(el: HTMLElement): void { const tpl = instantiateTemplate('#inkdrop-template'); el.appendChild(tpl); el.addEventListener('click', (e) => { @@ -199,10 +189,8 @@ export function setInkdropEffect(el) { /** * Instantiates template with the target selector. - * @param {string} selector - * @return {!DocumentFragment} */ -export function instantiateTemplate(selector) { +export function instantiateTemplate(selector: string): DocumentFragment { const tpl = dom.get(selector, HTMLTemplateElement); const doc = assertInstanceof( document.importNode(tpl.content, true), DocumentFragment); @@ -213,10 +201,10 @@ export function instantiateTemplate(selector) { /** * Creates JS module by given |scriptUrl| under untrusted context with given * origin and returns its proxy. - * @param {string} scriptUrl The URL of the script to load. - * @return {!Promise} + * @param scriptUrl The URL of the script to load. */ -export async function createUntrustedJSModule(scriptUrl) { +export async function createUntrustedJSModule(scriptUrl: string): + Promise { const untrustedPageReady = new WaitableEvent(); const iFrame = dom.create('iframe', HTMLIFrameElement); iFrame.addEventListener('load', () => untrustedPageReady.signal()); @@ -235,20 +223,17 @@ export async function createUntrustedJSModule(scriptUrl) { /** * Sleeps for a specified time. - * @param {number} ms Milliseconds to sleep. - * @return {!Promise} + * @param ms Milliseconds to sleep. */ -export function sleep(ms) { +export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Gets value in px of a property in a StylePropertyMapReadOnly - * @param {(!StylePropertyMapReadOnly|!StylePropertyMap)} style - * @param {string} prop - * @return {number} */ -export function getStyleValueInPx(style, prop) { +export function getStyleValueInPx( + style: (StylePropertyMapReadOnly|StylePropertyMap), prop: string): number { return assertInstanceof(style.get(prop), CSSNumericValue).to('px').value; } @@ -257,25 +242,15 @@ export function getStyleValueInPx(style, prop) { * before calling the first callback. */ export class DelayInterval { + private intervalId: number|null = null; + private readonly delayTimeoutId: number; /** - * @param {function(): void} callback - * @param {number} delayMs Delay milliseconds at start. - * @param {number} intervalMs Interval in milliseconds. - * @public + * @param delayMs Delay milliseconds at start. + * @param intervalMs Interval in milliseconds. */ - constructor(callback, delayMs, intervalMs) { - /** - * @type {?number} - * @private - */ - this.intervalId_ = null; - - /** - * @type {number} - * @private - */ - this.delayTimeoutId_ = setTimeout(() => { - this.intervalId_ = setInterval(() => { + constructor(callback: () => void, delayMs: number, intervalMs: number) { + this.delayTimeoutId = setTimeout(() => { + this.intervalId = setInterval(() => { callback(); }, intervalMs); callback(); @@ -285,21 +260,19 @@ export class DelayInterval { /** * Stop the interval. */ - stop() { - if (this.intervalId_ === null) { - clearTimeout(this.delayTimeoutId_); + stop(): void { + if (this.intervalId === null) { + clearTimeout(this.delayTimeoutId); } else { - clearInterval(this.intervalId_); + clearInterval(this.intervalId); } } } /** * Share file with share API. - * @param {!File} file - * @return {!Promise} */ -export async function share(file) { +export async function share(file: File): Promise { const shareData = {files: [file]}; try { if (!navigator.canShare(shareData)) { @@ -312,3 +285,16 @@ export async function share(file) { // message. } } + +/** + * Check if a string value is a variant of an enum. + * @param value value to be checked + * @return the value if it's an enum variant, null otherwise + */ +export function checkEnumVariant( + enumType: {[key: string]: T}, value: string|null): T { + if (value === null || !Object.values(enumType).includes(value)) { + return null; + } + return value as T; +} diff --git a/ash/webui/camera_app_ui/resources/js/views/camera.js b/ash/webui/camera_app_ui/resources/js/views/camera.js index 03e1965f2684a..52b33dd126057 100644 --- a/ash/webui/camera_app_ui/resources/js/views/camera.js +++ b/ash/webui/camera_app_ui/resources/js/views/camera.js @@ -1039,7 +1039,7 @@ export class Camera extends View { photoRs = await deviceOperator.getPhotoResolutions(deviceId); } else { resolCandidates = this.modes_.getFakeResolutionCandidates(mode, deviceId); - photoRs = resolCandidates; + photoRs = resolCandidates.map((c) => c.resolution); } const maxResolution = photoRs.reduce((maxR, r) => r.area > maxR.area ? r : maxR); diff --git a/ash/webui/camera_app_ui/resources/js/views/camera/mode/index.js b/ash/webui/camera_app_ui/resources/js/views/camera/mode/index.js index c84ece9cd3781..773f1258beb72 100644 --- a/ash/webui/camera_app_ui/resources/js/views/camera/mode/index.js +++ b/ash/webui/camera_app_ui/resources/js/views/camera/mode/index.js @@ -400,7 +400,7 @@ export class Modes { * @private */ get allModeNames_() { - return Object.keys(this.allModes_); + return Object.values(Mode); } /** diff --git a/ash/webui/camera_app_ui/resources/utils/cca.py b/ash/webui/camera_app_ui/resources/utils/cca.py index 19a005b5ad61c..eae6a861a040f 100755 --- a/ash/webui/camera_app_ui/resources/utils/cca.py +++ b/ash/webui/camera_app_ui/resources/utils/cca.py @@ -60,8 +60,8 @@ def build_preload_images_js(outdir): def gen_files_are_hard_links(gen_dir): cca_root = os.getcwd() - util_js = os.path.join(cca_root, 'js/util.js') - util_js_in_gen = os.path.join(gen_dir, 'js/util.js') + util_js = os.path.join(cca_root, 'js/util.ts') + util_js_in_gen = os.path.join(gen_dir, 'js/util.ts') return os.stat(util_js).st_ino == os.stat(util_js_in_gen).st_ino