diff --git a/abstract/UploaderBlock.js b/abstract/UploaderBlock.js index 1c9972f1e..34d71cb0d 100644 --- a/abstract/UploaderBlock.js +++ b/abstract/UploaderBlock.js @@ -21,7 +21,6 @@ import { TypedCollection } from './TypedCollection.js'; import { buildOutputCollectionState } from './buildOutputCollectionState.js'; import { uploadEntrySchema } from './uploadEntrySchema.js'; import { parseCdnUrl } from '../utils/parseCdnUrl.js'; - export class UploaderBlock extends ActivityBlock { couldBeCtxOwner = false; isCtxOwner = false; @@ -321,15 +320,17 @@ export class UploaderBlock extends ActivityBlock { this.fileInput.type = 'file'; this.fileInput.multiple = this.cfg.multiple; if (options.captureCamera) { - this.fileInput.capture = ''; - this.fileInput.accept = serializeCsv(IMAGE_ACCEPT_LIST); + this.fileInput.capture = this.cfg.cameraCapture; + this.fileInput.accept = 'image/*'; } else { this.fileInput.accept = accept; } this.fileInput.dispatchEvent(new MouseEvent('click')); this.fileInput.onchange = () => { // @ts-ignore TODO: fix this - [...this.fileInput['files']].forEach((file) => this.addFileFromObject(file, { source: UploadSource.LOCAL })); + [...this.fileInput['files']].forEach((file) => + this.addFileFromObject(file, { source: options.captureCamera ? UploadSource.CAMERA : UploadSource.LOCAL }), + ); // To call uploadTrigger UploadList should draw file items first: this.$['*currentActivity'] = ActivityBlock.activities.UPLOAD_LIST; this.setOrAddState('*modalActive', true); @@ -359,24 +360,18 @@ export class UploaderBlock extends ActivityBlock { this.setOrAddState('*modalActive', true); } else { if (this.sourceList?.length === 1) { - let srcKey = this.sourceList[0]; - // Single source case: - if (srcKey === 'local') { - this.$['*currentActivity'] = ActivityBlock.activities.UPLOAD_LIST; - this?.['openSystemDialog'](); - } else { - if (Object.values(UploaderBlock.extSrcList).includes(/** @type {any} */ (srcKey))) { - this.set$({ - '*currentActivityParams': { - externalSourceType: srcKey, - }, - '*currentActivity': ActivityBlock.activities.EXTERNAL, - }); - } else { - this.$['*currentActivity'] = srcKey; - } - this.setOrAddState('*modalActive', true); - } + const srcKey = this.sourceList[0]; + /** @type {Set} */ + const blocksRegistry = this.$['*blocksRegistry']; + /** + * @param {import('./Block').Block} block + * @returns {block is import('../blocks/SourceBtn/SourceBtn.js').SourceBtn} + */ + const isSourceBtn = (block) => 'type' in block && block.type === srcKey; + const sourceBtnBlock = [...blocksRegistry].find(isSourceBtn); + // TODO: This is weird that we have this logic inside UI component, we should consider to move it somewhere else + sourceBtnBlock?.activate(); + this.setOrAddState('*modalActive', true); } else { // Multiple sources case: this.set$({ diff --git a/blocks/CameraSource/CameraSource.js b/blocks/CameraSource/CameraSource.js index c5a241382..36605a5b0 100644 --- a/blocks/CameraSource/CameraSource.js +++ b/blocks/CameraSource/CameraSource.js @@ -165,18 +165,19 @@ export class CameraSource extends UploaderBlock { this._canvas.width = this.ref.video['videoWidth']; // @ts-ignore this._ctx.drawImage(this.ref.video, 0, 0); - let date = Date.now(); - let name = `camera-${date}.png`; + const date = Date.now(); + const name = `camera-${date}.jpeg`; + const format = 'image/jpeg'; this._canvas.toBlob((blob) => { let file = new File([blob], name, { lastModified: date, - type: 'image/png', + type: format, }); this.addFileFromObject(file, { source: UploadSource.CAMERA }); this.set$({ '*currentActivity': ActivityBlock.activities.UPLOAD_LIST, }); - }); + }, format); } async initCallback() { diff --git a/blocks/Config/initialConfig.js b/blocks/Config/initialConfig.js index 33b6a6f1f..f834b7485 100644 --- a/blocks/Config/initialConfig.js +++ b/blocks/Config/initialConfig.js @@ -20,6 +20,7 @@ export const initialConfig = { externalSourcesPreferredTypes: '', store: 'auto', cameraMirror: false, + cameraCapture: '', sourceList: 'local, url, camera, dropbox, gdrive', cloudImageEditorTabs: serializeCsv(ALL_TABS), maxLocalFileSizeBytes: 0, diff --git a/blocks/Config/normalizeConfigValue.js b/blocks/Config/normalizeConfigValue.js index e238f4400..835e234d0 100644 --- a/blocks/Config/normalizeConfigValue.js +++ b/blocks/Config/normalizeConfigValue.js @@ -27,6 +27,15 @@ export const asBoolean = (value) => { /** @param {unknown} value */ const asStore = (value) => (value === 'auto' ? value : asBoolean(value)); +/** @param {unknown} value */ +const asCameraCapture = (value) => { + const strValue = asString(value); + if (strValue !== 'user' && strValue !== 'environment' && strValue !== '') { + throw new Error(`Invalid "cameraCapture" value: "${strValue}"`); + } + return strValue; +}; + /** * @type {{ * [Key in keyof import('../../types').ConfigPlainType]: ( @@ -46,6 +55,7 @@ const mapping = { externalSourcesPreferredTypes: asString, store: asStore, cameraMirror: asBoolean, + cameraCapture: asCameraCapture, sourceList: asString, maxLocalFileSizeBytes: asNumber, thumbSize: asNumber, diff --git a/blocks/SourceBtn/SourceBtn.js b/blocks/SourceBtn/SourceBtn.js index a4d357efd..e67288634 100644 --- a/blocks/SourceBtn/SourceBtn.js +++ b/blocks/SourceBtn/SourceBtn.js @@ -1,23 +1,45 @@ +// @ts-check import { UploaderBlock } from '../../abstract/UploaderBlock.js'; import { ActivityBlock } from '../../abstract/ActivityBlock.js'; const L10N_PREFIX = 'src-type-'; +/** + * @typedef {{ + * type: string; + * activity?: string; + * textKey?: string; + * icon?: string; + * activate?: () => boolean; + * activityParams?: Record; + * }} TConfig + */ + export class SourceBtn extends UploaderBlock { couldBeCtxOwner = true; - /** @private */ + /** @type {string | undefined} */ + type = undefined; + /** + * @private + * @type {Record} + */ _registeredTypes = {}; - init$ = { - ...this.init$, - iconName: 'default', - }; + constructor() { + super(); + + this.init$ = { + ...this.init$, + iconName: 'default', + }; + } initTypes() { this.registerType({ type: UploaderBlock.sourceTypes.LOCAL, - onClick: () => { + activate: () => { this.openSystemDialog(); + return false; }, }); this.registerType({ @@ -28,9 +50,8 @@ export class SourceBtn extends UploaderBlock { this.registerType({ type: UploaderBlock.sourceTypes.CAMERA, activity: ActivityBlock.activities.CAMERA, - onClick: () => { - let el = document.createElement('input'); - var supportsCapture = el.capture !== undefined; + activate: () => { + const supportsCapture = 'capture' in document.createElement('input'); if (supportsCapture) { this.openSystemDialog({ captureCamera: true }); } @@ -59,39 +80,55 @@ export class SourceBtn extends UploaderBlock { this.initTypes(); this.setAttribute('role', 'button'); - this.defineAccessor('type', (val) => { - if (!val) { - return; - } - this.applyType(val); - }); + this.defineAccessor( + 'type', + /** @param {string} val */ + (val) => { + if (!val) { + return; + } + this.applyType(val); + }, + ); } + /** @param {TConfig} typeConfig */ registerType(typeConfig) { this._registeredTypes[typeConfig.type] = typeConfig; } + /** @param {string} type */ getType(type) { return this._registeredTypes[type]; } + activate() { + if (!this.type) { + return; + } + const configType = this._registeredTypes[this.type]; + const { activity, activate, activityParams = {} } = configType; + const showActivity = activate ? activate() : !!activity; + showActivity && + this.set$({ + '*currentActivityParams': activityParams, + '*currentActivity': activity, + }); + } + + /** @param {string} type */ applyType(type) { const configType = this._registeredTypes[type]; if (!configType) { console.warn('Unsupported source type: ' + type); return; } - const { textKey = type, icon = type, activity, onClick, activityParams = {} } = configType; + const { textKey = type, icon = type } = configType; this.applyL10nKey('src-type', `${L10N_PREFIX}${textKey}`); this.$.iconName = icon; - this.onclick = (e) => { - const showActivity = onClick ? onClick(e) : !!activity; - showActivity && - this.set$({ - '*currentActivityParams': activityParams, - '*currentActivity': activity, - }); + this.onclick = () => { + this.activate(); }; } } @@ -100,5 +137,6 @@ SourceBtn.template = /* HTML */ `
`; SourceBtn.bindAttributes({ + // @ts-expect-error symbiote types bug type: null, }); diff --git a/types/exported.d.ts b/types/exported.d.ts index 1bde9d33c..d23a3bd48 100644 --- a/types/exported.d.ts +++ b/types/exported.d.ts @@ -15,6 +15,7 @@ export type ConfigType = { externalSourcesPreferredTypes: string; store: boolean | 'auto'; cameraMirror: boolean; + cameraCapture: 'user' | 'environment' | ''; sourceList: string; maxLocalFileSizeBytes: number; thumbSize: number;