From fdfda4c89efb5087273c74ae6e251ca8ee5f4097 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Tue, 15 Oct 2024 13:31:07 +0300 Subject: [PATCH 01/12] feat: new social sources --- blocks/ExternalSource/ExternalSource.js | 169 ++++++-------- blocks/ExternalSource/MessageBridge.js | 206 ++++++++++++++++++ blocks/ExternalSource/buildStyles.js | 133 ----------- blocks/ExternalSource/buildThemeDefinition.js | 79 +++++++ blocks/ExternalSource/external-source.css | 42 +++- blocks/ExternalSource/messages.js | 35 --- locales/file-uploader/en.js | 4 +- 7 files changed, 393 insertions(+), 275 deletions(-) create mode 100644 blocks/ExternalSource/MessageBridge.js delete mode 100644 blocks/ExternalSource/buildStyles.js create mode 100644 blocks/ExternalSource/buildThemeDefinition.js delete mode 100644 blocks/ExternalSource/messages.js diff --git a/blocks/ExternalSource/ExternalSource.js b/blocks/ExternalSource/ExternalSource.js index 549fa3ce0..1b6474485 100644 --- a/blocks/ExternalSource/ExternalSource.js +++ b/blocks/ExternalSource/ExternalSource.js @@ -5,31 +5,12 @@ import { ActivityBlock } from '../../abstract/ActivityBlock.js'; import { UploaderBlock } from '../../abstract/UploaderBlock.js'; import { stringToArray } from '../../utils/stringToArray.js'; import { wildcardRegexp } from '../../utils/wildcardRegexp.js'; -import { buildStyles } from './buildStyles.js'; -import { registerMessage, unregisterMessage } from './messages.js'; +import { buildThemeDefinition } from './buildThemeDefinition.js'; +import { MessageBridge } from './MessageBridge.js'; import { queryString } from './query-string.js'; /** @typedef {{ externalSourceType: string }} ActivityParams */ -/** - * @typedef {{ - * type: 'file-selected'; - * obj_type: 'selected_file'; - * filename: string; - * url: string; - * alternatives?: Record; - * }} SelectedFileMessage - */ - -/** - * @typedef {{ - * type: 'embed-css'; - * style: string; - * }} EmbedCssMessage - */ - -/** @typedef {SelectedFileMessage | EmbedCssMessage} Message */ - export class ExternalSource extends UploaderBlock { couldBeCtxOwner = true; activityType = ActivityBlock.activities.EXTERNAL; @@ -41,12 +22,20 @@ export class ExternalSource extends UploaderBlock { ...this.init$, activityIcon: '', activityCaption: '', + + /** @type {import('./MessageBridge.js').InputMessageMap['selected-files-change']['selectedFiles']} */ selectedList: [], - counter: 0, - multiple: false, + total: 0, + + isSelectionReady: false, + couldSelectAll: false, + couldDeselectAll: false, + showSelectionStatus: false, + counterText: '', + onDone: () => { for (const message of this.$.selectedList) { - const url = this.extractUrlFromMessage(message); + const url = this.extractUrlFromSelectedFile(message); const { filename } = message; const { externalSourceType } = this.activityParams; this.api.addFileFromUrl(url, { fileName: filename, source: externalSourceType }); @@ -57,6 +46,14 @@ export class ExternalSource extends UploaderBlock { onCancel: () => { this.historyBack(); }, + + onSelectAll: () => { + this._messageBridge?.send({ type: 'select-all' }); + }, + + onDeselectAll: () => { + this._messageBridge?.send({ type: 'deselect-all' }); + }, }; } @@ -69,12 +66,6 @@ export class ExternalSource extends UploaderBlock { throw new Error(`External Source activity params not found`); } - /** - * @private - * @type {HTMLIFrameElement | null} - */ - _iframe = null; - initCallback() { super.initCallback(); this.registerActivity(this.activityType, { @@ -108,24 +99,23 @@ export class ExternalSource extends UploaderBlock { this.unmountIframe(); } }); - this.sub('selectedList', (list) => { - this.$.counter = list.length; - }); this.subConfigValue('multiple', (multiple) => { - this.$.multiple = multiple; + this.$.showSelectionStatus = multiple; }); } /** * @private - * @param {SelectedFileMessage} message + * @param {NonNullable< + * import('./MessageBridge.js').InputMessageMap['selected-files-change']['selectedFiles'] + * >[number]} selectedFile */ - extractUrlFromMessage(message) { - if (message.alternatives) { + extractUrlFromSelectedFile(selectedFile) { + if (selectedFile.alternatives) { const preferredTypes = stringToArray(this.cfg.externalSourcesPreferredTypes); for (const preferredType of preferredTypes) { const regexp = wildcardRegexp(preferredType); - for (const [type, typeUrl] of Object.entries(message.alternatives)) { + for (const [type, typeUrl] of Object.entries(selectedFile.alternatives)) { if (regexp.test(type)) { return typeUrl; } @@ -133,31 +123,33 @@ export class ExternalSource extends UploaderBlock { } } - return message.url; - } - - /** - * @private - * @param {Message} message - */ - sendMessage(message) { - this._iframe?.contentWindow?.postMessage(JSON.stringify(message), '*'); + return selectedFile.url; } /** * @private - * @param {SelectedFileMessage} message + * @param {import('./MessageBridge.js').InputMessageMap['selected-files-change']} message */ - async handleFileSelected(message) { - if (!this.$.multiple && this.$.selectedList.length) { + async handleSelectedFilesChange(message) { + if (this.cfg.multiple !== message.isMultipleMode) { + console.error('Multiple mode mismatch'); return; } - this.$.selectedList = [...this.$.selectedList, message]; - - if (!this.$.multiple) { - this.$.onDone(); - } + this.bindL10n('counterText', () => + this.l10n('selected-count', { + count: message.selectedCount, + total: message.total, + }), + ); + + this.set$({ + isSelectionReady: message.isReady, + showSelectionStatus: message.isMultipleMode && message.total > 0, + couldSelectAll: message.selectedCount < message.total, + couldDeselectAll: message.selectedCount === message.total, + selectedList: message.selectedFiles, + }); } /** @private */ @@ -165,49 +157,28 @@ export class ExternalSource extends UploaderBlock { this.applyStyles(); } - /** - * @private - * @param {string} propName - */ - getCssValue(propName) { - let style = window.getComputedStyle(this); - return style.getPropertyValue(propName).trim(); - } - /** @private */ applyStyles() { - let colors = { - radius: this.getCssValue('--uc-radius'), - backgroundColor: this.getCssValue('--uc-background'), - textColor: this.getCssValue('--uc-foreground'), - secondaryColor: this.getCssValue('--uc-secondary'), - secondaryForegroundColor: this.getCssValue('--uc-secondary-foreground'), - secondaryHover: this.getCssValue('--uc-secondary-hover'), - linkColor: this.getCssValue('--uc-primary'), - linkColorHover: this.getCssValue('--uc-primary-hover'), - fontFamily: this.getCssValue('--uc-font-family'), - fontSize: this.getCssValue('--uc-font-size'), - }; - - this.sendMessage({ - type: 'embed-css', - style: buildStyles(colors), + this._messageBridge?.send({ + type: 'set-theme-definition', + theme: buildThemeDefinition(this), }); } /** @private */ remoteUrl() { - const { pubkey, remoteTabSessionKey, socialBaseUrl } = this.cfg; + const { pubkey, remoteTabSessionKey, socialBaseUrl, multiple } = this.cfg; const { externalSourceType } = this.activityParams; const lang = this.l10n('social-source-lang')?.split('-')?.[0] || 'en'; const params = { lang, public_key: pubkey, images_only: false.toString(), - pass_window_open: false, session_key: remoteTabSessionKey, + wait_for_theme: true, + multiple: multiple.toString(), }; - const url = new URL(`/window3/${externalSourceType}`, socialBaseUrl); + const url = new URL(`/window4/${externalSourceType}`, socialBaseUrl); url.search = queryString(params); return url.toString(); } @@ -231,31 +202,30 @@ export class ExternalSource extends UploaderBlock { this.ref.iframeWrapper.innerHTML = ''; this.ref.iframeWrapper.appendChild(iframe); - registerMessage('file-selected', iframe.contentWindow, this.handleFileSelected.bind(this)); + if (!iframe.contentWindow) { + return; + } + + this._messageBridge?.destroy(); + + /** @private */ + this._messageBridge = new MessageBridge(iframe.contentWindow); + this._messageBridge.on('selected-files-change', this.handleSelectedFilesChange.bind(this)); - this._iframe = iframe; this.$.selectedList = []; } /** @private */ unmountIframe() { - this._iframe && unregisterMessage('file-selected', this._iframe.contentWindow); + this._messageBridge?.destroy(); + this._messageBridge = undefined; this.ref.iframeWrapper.innerHTML = ''; - this._iframe = null; this.$.selectedList = []; - this.$.counter = 0; } } ExternalSource.template = /* HTML */ ` - -
- - {{activityCaption}} -
-
-
{{counter}}
+
+ {{counterText}} + + +
diff --git a/blocks/ExternalSource/MessageBridge.js b/blocks/ExternalSource/MessageBridge.js new file mode 100644 index 000000000..f2358b69b --- /dev/null +++ b/blocks/ExternalSource/MessageBridge.js @@ -0,0 +1,206 @@ +// @ts-check + +/** + * @typedef {{ + * caption?: string | null; + * created: number; + * id: string; + * public_page?: string; + * username: string; + * size?: number; + * name?: string; + * modified?: number; + * }} InstagramInfo + */ + +/** + * @typedef {{ + * error?: undefined; + * info?: InstagramInfo; + * alternatives?: Record; + * is_image?: boolean | null; + * filename?: string; + * obj_type: 'selected_file'; + * url: string; + * }} DoneSuccessResponse + */ + +/** + * @typedef {{ + * 'selected-files-change': { + * type: 'selected-files-change'; + * total: number; + * selectedCount: number; + * } & ( + * | { + * isReady: false; + * isMultipleMode: boolean; + * selectedFiles: undefined; + * } + * | { + * isReady: true; + * isMultipleMode: true; + * selectedFiles: DoneSuccessResponse[]; + * } + * | { + * isReady: true; + * isMultipleMode: false; + * selectedFiles: [DoneSuccessResponse] | []; + * } + * ); + * }} InputMessageMap + */ + +/** @typedef {keyof InputMessageMap} InputMessageType */ +/** @typedef {InputMessageMap[InputMessageType]} InputMessage */ + +/** + * @typedef {{ + * '--uc-font-family': string; + * '--uc-font-size': string; + * '--uc-line-height': string; + * '--uc-button-size': string; + * '--uc-preview-size': string; + * '--uc-input-size': string; + * '--uc-padding': string; + * '--uc-radius': string; + * '--uc-transition': string; + * '--uc-background': string; + * '--uc-foreground': string; + * '--uc-primary': string; + * '--uc-primary-hover': string; + * '--uc-primary-transparent': string; + * '--uc-primary-foreground': string; + * '--uc-secondary': string; + * '--uc-secondary-hover': string; + * '--uc-secondary-foreground': string; + * '--uc-muted': string; + * '--uc-muted-foreground': string; + * '--uc-destructive': string; + * '--uc-destructive-foreground': string; + * '--uc-border': string; + * '--uc-primary-rgb-light': string; + * '--uc-primary-light': string; + * '--uc-primary-hover-light': string; + * '--uc-primary-transparent-light': string; + * '--uc-background-light': string; + * '--uc-foreground-light': string; + * '--uc-primary-foreground-light': string; + * '--uc-secondary-light': string; + * '--uc-secondary-hover-light': string; + * '--uc-secondary-foreground-light': string; + * '--uc-muted-light': string; + * '--uc-muted-foreground-light': string; + * '--uc-destructive-light': string; + * '--uc-destructive-foreground-light': string; + * '--uc-border-light': string; + * '--uc-primary-rgb-dark': string; + * '--uc-primary-dark': string; + * '--uc-primary-hover-dark': string; + * '--uc-primary-transparent-dark': string; + * '--uc-background-dark': string; + * '--uc-foreground-dark': string; + * '--uc-primary-foreground-dark': string; + * '--uc-secondary-dark': string; + * '--uc-secondary-hover-dark': string; + * '--uc-secondary-foreground-dark': string; + * '--uc-muted-dark': string; + * '--uc-muted-foreground-dark': string; + * '--uc-destructive-dark': string; + * '--uc-destructive-foreground-dark': string; + * '--uc-border-dark': string; + * '--uc-primary-oklch-light': string; + * '--uc-primary-oklch-dark': string; + * }} ThemeDefinition + */ + +/** + * @typedef {{ + * type: 'select-all'; + * } + * | { + * type: 'deselect-all'; + * } + * | { + * type: 'set-theme-definition'; + * theme: Record; + * } + * | { + * type: 'set-embed-css'; + * css: Partial; + * }} OutputMessage + */ + +/** @type {InputMessageType[]} */ +const MESSAGE_TYPE_WHITELIST = ['selected-files-change']; + +/** + * @template {InputMessageType} T + * @typedef {(message: InputMessageMap[T]) => void} InputMessageHandler + */ + +/** + * @param {unknown} message + * @returns {message is InputMessageMap[InputMessageType]} + */ +const isWhitelistedMessage = (message) => { + if (!message) return false; + if (typeof message !== 'object') return false; + return 'type' in message && MESSAGE_TYPE_WHITELIST.includes(/** @type {InputMessageType} */ (message.type)); +}; + +export class MessageBridge { + /** @type {Map>>} */ + _handlerMap = new Map(); + + /** @type {Window} */ + _context; + + /** @param {Window} context */ + constructor(context) { + this._context = context; + + window.addEventListener('message', this._handleMessage); + } + + /** @param {MessageEvent} e */ + _handleMessage = (e) => { + if (e.source !== this._context) { + return; + } + const message = e.data; + if (!isWhitelistedMessage(message)) { + return; + } + + const handlers = this._handlerMap.get(message.type); + if (handlers) { + for (const handler of handlers) { + handler(message); + } + } + }; + + /** + * @template {InputMessageType} T + * @param {T} type + * @param {InputMessageHandler} handler + */ + on(type, handler) { + const handlers = this._handlerMap.get(type) ?? new Set(); + if (!this._handlerMap.has(type)) { + this._handlerMap.set(type, handlers); + } + + handlers.add(/** @type {InputMessageHandler} */ (handler)); + } + + /** @param {OutputMessage} message */ + send(message) { + this._context.postMessage(message, '*'); + } + + destroy() { + window.removeEventListener('message', this._handleMessage); + } +} diff --git a/blocks/ExternalSource/buildStyles.js b/blocks/ExternalSource/buildStyles.js deleted file mode 100644 index 388d349f4..000000000 --- a/blocks/ExternalSource/buildStyles.js +++ /dev/null @@ -1,133 +0,0 @@ -// @ts-check - -/** - * @param {Record>} style - * @returns - */ -const styleToCss = (style) => { - const css = Object.keys(style).reduce((acc, selector) => { - const propertiesObj = style[selector]; - const propertiesStr = Object.keys(propertiesObj).reduce((acc, prop) => { - const value = propertiesObj[prop]; - return acc + `${prop}: ${value};`; - }, ''); - return acc + `${selector}{${propertiesStr}}`; - }, ''); - return css; -}; - -/** - * @param {{ - * textColor: string; - * backgroundColor: string; - * linkColor: string; - * linkColorHover: string; - * secondaryColor: string; - * secondaryHover: string; - * secondaryForegroundColor: string; - * fontFamily: string; - * fontSize: string; - * radius: string; - * }} options - */ -export function buildStyles({ - textColor, - backgroundColor, - linkColor, - linkColorHover, - secondaryColor, - secondaryHover, - secondaryForegroundColor, - fontFamily, - fontSize, - radius, -}) { - const border = `solid 1px ${secondaryColor}`; - - // TODO: we need to update source source styles, add css custom properties to control theme - return styleToCss({ - body: { - color: textColor, - 'background-color': backgroundColor, - 'font-family': fontFamily, - 'font-size': fontSize, - }, - '.side-bar': { - background: 'inherit', - 'border-right': border, - }, - '.main-content': { - background: 'inherit', - }, - '.main-content-header': { - background: 'inherit', - }, - '.main-content-footer': { - background: 'inherit', - }, - '.list-table-row': { - color: 'inherit', - }, - '.list-table-row:hover': { - background: secondaryColor, - }, - '.list-table-row .list-table-cell-a, .list-table-row .list-table-cell-b': { - 'border-top': border, - }, - '.list-table-body .list-items': { - 'border-bottom': border, - }, - '.bread-crumbs a': { - color: linkColor, - }, - '.bread-crumbs a:hover': { - color: linkColorHover, - }, - '.main-content.loading': { - background: `${backgroundColor} url(/static/images/loading_spinner.gif) center no-repeat`, - 'background-size': '25px 25px', - }, - '.list-icons-item': { - 'background-color': secondaryColor, - }, - '.source-gdrive .side-bar-menu a, .source-gphotos .side-bar-menu a': { - color: linkColor, - }, - '.source-gdrive .side-bar-menu a, .source-gphotos .side-bar-menu a:hover': { - color: linkColorHover, - }, - '.side-bar-menu a': { - color: linkColor, - }, - '.side-bar-menu a:hover': { - color: linkColorHover, - }, - '.source-gdrive .side-bar-menu .current, .source-gdrive .side-bar-menu a:hover, .source-gphotos .side-bar-menu .current, .source-gphotos .side-bar-menu a:hover': - { - color: linkColorHover, - }, - '.source-vk .side-bar-menu a': { - color: linkColor, - }, - '.source-vk .side-bar-menu a:hover': { - color: linkColorHover, - background: 'none', - }, - 'input[type=submit], .button, button': { - color: secondaryForegroundColor, - background: secondaryColor, - 'box-shadow': 'none', - border: 'none', - 'border-radius': radius, - }, - 'input[type=submit]:hover, .button:hover, button:hover': { - background: secondaryHover, - }, - '.text-field, input[type=search], input[type=text], input[type=url], textarea': { - color: secondaryForegroundColor, - 'border-radius': radius, - background: secondaryColor, - border, - }, - }); -} diff --git a/blocks/ExternalSource/buildThemeDefinition.js b/blocks/ExternalSource/buildThemeDefinition.js new file mode 100644 index 000000000..c8eb7682a --- /dev/null +++ b/blocks/ExternalSource/buildThemeDefinition.js @@ -0,0 +1,79 @@ +// @ts-check + +/** + * @param {HTMLElement} element + * @param {string} propName + */ +function getCssValue(element, propName) { + let style = window.getComputedStyle(element); + return style.getPropertyValue(propName).trim(); +} + +const ucCustomProperties = /** @type {(keyof import('./MessageBridge.js').ThemeDefinition)[]} */ ([ + '--uc-font-family', + '--uc-font-size', + '--uc-line-height', + '--uc-button-size', + '--uc-preview-size', + '--uc-input-size', + '--uc-padding', + '--uc-radius', + '--uc-transition', + '--uc-background', + '--uc-foreground', + '--uc-primary', + '--uc-primary-hover', + '--uc-primary-transparent', + '--uc-primary-foreground', + '--uc-secondary', + '--uc-secondary-hover', + '--uc-secondary-foreground', + '--uc-muted', + '--uc-muted-foreground', + '--uc-destructive', + '--uc-destructive-foreground', + '--uc-border', + '--uc-primary-rgb-light', + '--uc-primary-light', + '--uc-primary-hover-light', + '--uc-primary-transparent-light', + '--uc-background-light', + '--uc-foreground-light', + '--uc-primary-foreground-light', + '--uc-secondary-light', + '--uc-secondary-hover-light', + '--uc-secondary-foreground-light', + '--uc-muted-light', + '--uc-muted-foreground-light', + '--uc-destructive-light', + '--uc-destructive-foreground-light', + '--uc-border-light', + '--uc-primary-rgb-dark', + '--uc-primary-dark', + '--uc-primary-hover-dark', + '--uc-primary-transparent-dark', + '--uc-background-dark', + '--uc-foreground-dark', + '--uc-primary-foreground-dark', + '--uc-secondary-dark', + '--uc-secondary-hover-dark', + '--uc-secondary-foreground-dark', + '--uc-muted-dark', + '--uc-muted-foreground-dark', + '--uc-destructive-dark', + '--uc-destructive-foreground-dark', + '--uc-border-dark', + '--uc-primary-oklch-light', + '--uc-primary-oklch-dark', +]); + +/** @param {HTMLElement} element */ +export function buildThemeDefinition(element) { + return ucCustomProperties.reduce((acc, prop) => { + const value = getCssValue(element, prop); + if (value) { + acc[prop] = value; + } + return acc; + }, /** @type {Record} */ ({})); +} diff --git a/blocks/ExternalSource/external-source.css b/blocks/ExternalSource/external-source.css index a7284fbd4..7d875c0fd 100644 --- a/blocks/ExternalSource/external-source.css +++ b/blocks/ExternalSource/external-source.css @@ -5,6 +5,7 @@ uc-external-source { height: 100%; background-color: var(--uc-background); overflow: hidden; + position: relative; } [uc-modal] uc-external-source { @@ -42,11 +43,11 @@ uc-external-source .uc-iframe-wrapper { } uc-external-source .uc-toolbar { - display: grid; + display: flex; + width: 100%; grid-gap: var(--uc-padding); - grid-template-columns: max-content 1fr max-content max-content; align-items: center; - width: 100%; + justify-content: space-between; padding: var(--uc-padding); border-top: 1px solid var(--uc-border); } @@ -55,11 +56,36 @@ uc-external-source .uc-back-btn { padding-left: 0; } -uc-external-source .uc-selected-counter { +uc-external-source .uc-selection-status-box { + color: var(--uc-foreground); display: flex; - grid-gap: var(--uc-padding); + flex-direction: column; align-items: center; - justify-content: space-between; - padding: var(--uc-padding); - color: var(--uc-muted-foreground); + justify-content: center; +} + +uc-external-source .uc-selection-status-box button { + color: var(--uc-primary); + height: auto; + padding: 0; + background: none; +} + +uc-external-source .uc-selection-status-box button:hover { + text-decoration: underline; +} + +uc-external-source uc-activity-header { + position: absolute; + width: 100%; + justify-content: flex-end; + z-index: 1; + left: 0; + top: 0; + right: 0; + pointer-events: none; +} + +uc-external-source uc-activity-header .uc-close-btn { + pointer-events: auto; } diff --git a/blocks/ExternalSource/messages.js b/blocks/ExternalSource/messages.js deleted file mode 100644 index 290a7440e..000000000 --- a/blocks/ExternalSource/messages.js +++ /dev/null @@ -1,35 +0,0 @@ -let cbMapping = {}; - -window.addEventListener('message', (e) => { - let message; - try { - message = JSON.parse(e.data); - } catch (err) { - return; - } - - if (message?.type in cbMapping) { - let cbList = cbMapping[message.type]; - for (let [sender, callback] of cbList) { - if (e.source === sender) { - callback(message); - } - } - } -}); - -const registerMessage = function (type, sender, callback) { - if (!(type in cbMapping)) { - cbMapping[type] = []; - } - - cbMapping[type].push([sender, callback]); -}; - -const unregisterMessage = function (type, sender) { - if (type in cbMapping) { - cbMapping[type] = cbMapping[type].filter((item) => item[0] !== sender); - } -}; - -export { registerMessage, unregisterMessage }; diff --git a/locales/file-uploader/en.js b/locales/file-uploader/en.js index b465f33d0..bbab560af 100644 --- a/locales/file-uploader/en.js +++ b/locales/file-uploader/en.js @@ -26,7 +26,9 @@ export default { yes: 'Yes', 'confirm-your-action': 'Confirm your action', 'are-you-sure': 'Are you sure?', - 'selected-count': 'Selected:', + 'selected-count': '{{count}} of {{total}} selected', + 'select-all': 'Select all', + 'deselect-all': 'Deselect all', 'upload-error': 'Upload error', 'validation-error': 'Validation error', 'no-files': 'No files selected', From f85613656539fdcc5f3b2b3a3a0accebc14b0005 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Tue, 15 Oct 2024 13:34:43 +0300 Subject: [PATCH 02/12] chore: extract types to separate file --- blocks/ExternalSource/ExternalSource.js | 8 +- blocks/ExternalSource/MessageBridge.js | 157 ++---------------- blocks/ExternalSource/buildThemeDefinition.js | 4 +- blocks/ExternalSource/types.js | 139 ++++++++++++++++ 4 files changed, 155 insertions(+), 153 deletions(-) create mode 100644 blocks/ExternalSource/types.js diff --git a/blocks/ExternalSource/ExternalSource.js b/blocks/ExternalSource/ExternalSource.js index 1b6474485..b595da465 100644 --- a/blocks/ExternalSource/ExternalSource.js +++ b/blocks/ExternalSource/ExternalSource.js @@ -23,7 +23,7 @@ export class ExternalSource extends UploaderBlock { activityIcon: '', activityCaption: '', - /** @type {import('./MessageBridge.js').InputMessageMap['selected-files-change']['selectedFiles']} */ + /** @type {import('./types.js').InputMessageMap['selected-files-change']['selectedFiles']} */ selectedList: [], total: 0, @@ -106,9 +106,7 @@ export class ExternalSource extends UploaderBlock { /** * @private - * @param {NonNullable< - * import('./MessageBridge.js').InputMessageMap['selected-files-change']['selectedFiles'] - * >[number]} selectedFile + * @param {NonNullable[number]} selectedFile */ extractUrlFromSelectedFile(selectedFile) { if (selectedFile.alternatives) { @@ -128,7 +126,7 @@ export class ExternalSource extends UploaderBlock { /** * @private - * @param {import('./MessageBridge.js').InputMessageMap['selected-files-change']} message + * @param {import('./types.js').InputMessageMap['selected-files-change']} message */ async handleSelectedFilesChange(message) { if (this.cfg.multiple !== message.isMultipleMode) { diff --git a/blocks/ExternalSource/MessageBridge.js b/blocks/ExternalSource/MessageBridge.js index f2358b69b..6c3f304fd 100644 --- a/blocks/ExternalSource/MessageBridge.js +++ b/blocks/ExternalSource/MessageBridge.js @@ -1,156 +1,21 @@ -// @ts-check - -/** - * @typedef {{ - * caption?: string | null; - * created: number; - * id: string; - * public_page?: string; - * username: string; - * size?: number; - * name?: string; - * modified?: number; - * }} InstagramInfo - */ - -/** - * @typedef {{ - * error?: undefined; - * info?: InstagramInfo; - * alternatives?: Record; - * is_image?: boolean | null; - * filename?: string; - * obj_type: 'selected_file'; - * url: string; - * }} DoneSuccessResponse - */ - -/** - * @typedef {{ - * 'selected-files-change': { - * type: 'selected-files-change'; - * total: number; - * selectedCount: number; - * } & ( - * | { - * isReady: false; - * isMultipleMode: boolean; - * selectedFiles: undefined; - * } - * | { - * isReady: true; - * isMultipleMode: true; - * selectedFiles: DoneSuccessResponse[]; - * } - * | { - * isReady: true; - * isMultipleMode: false; - * selectedFiles: [DoneSuccessResponse] | []; - * } - * ); - * }} InputMessageMap - */ - -/** @typedef {keyof InputMessageMap} InputMessageType */ -/** @typedef {InputMessageMap[InputMessageType]} InputMessage */ - -/** - * @typedef {{ - * '--uc-font-family': string; - * '--uc-font-size': string; - * '--uc-line-height': string; - * '--uc-button-size': string; - * '--uc-preview-size': string; - * '--uc-input-size': string; - * '--uc-padding': string; - * '--uc-radius': string; - * '--uc-transition': string; - * '--uc-background': string; - * '--uc-foreground': string; - * '--uc-primary': string; - * '--uc-primary-hover': string; - * '--uc-primary-transparent': string; - * '--uc-primary-foreground': string; - * '--uc-secondary': string; - * '--uc-secondary-hover': string; - * '--uc-secondary-foreground': string; - * '--uc-muted': string; - * '--uc-muted-foreground': string; - * '--uc-destructive': string; - * '--uc-destructive-foreground': string; - * '--uc-border': string; - * '--uc-primary-rgb-light': string; - * '--uc-primary-light': string; - * '--uc-primary-hover-light': string; - * '--uc-primary-transparent-light': string; - * '--uc-background-light': string; - * '--uc-foreground-light': string; - * '--uc-primary-foreground-light': string; - * '--uc-secondary-light': string; - * '--uc-secondary-hover-light': string; - * '--uc-secondary-foreground-light': string; - * '--uc-muted-light': string; - * '--uc-muted-foreground-light': string; - * '--uc-destructive-light': string; - * '--uc-destructive-foreground-light': string; - * '--uc-border-light': string; - * '--uc-primary-rgb-dark': string; - * '--uc-primary-dark': string; - * '--uc-primary-hover-dark': string; - * '--uc-primary-transparent-dark': string; - * '--uc-background-dark': string; - * '--uc-foreground-dark': string; - * '--uc-primary-foreground-dark': string; - * '--uc-secondary-dark': string; - * '--uc-secondary-hover-dark': string; - * '--uc-secondary-foreground-dark': string; - * '--uc-muted-dark': string; - * '--uc-muted-foreground-dark': string; - * '--uc-destructive-dark': string; - * '--uc-destructive-foreground-dark': string; - * '--uc-border-dark': string; - * '--uc-primary-oklch-light': string; - * '--uc-primary-oklch-dark': string; - * }} ThemeDefinition - */ - -/** - * @typedef {{ - * type: 'select-all'; - * } - * | { - * type: 'deselect-all'; - * } - * | { - * type: 'set-theme-definition'; - * theme: Record; - * } - * | { - * type: 'set-embed-css'; - * css: Partial; - * }} OutputMessage - */ - -/** @type {InputMessageType[]} */ +/** @type {import('./types').InputMessageType[]} */ const MESSAGE_TYPE_WHITELIST = ['selected-files-change']; -/** - * @template {InputMessageType} T - * @typedef {(message: InputMessageMap[T]) => void} InputMessageHandler - */ - /** * @param {unknown} message - * @returns {message is InputMessageMap[InputMessageType]} + * @returns {message is import("./types").InputMessageMap[import("./types").InputMessageType]} */ const isWhitelistedMessage = (message) => { if (!message) return false; if (typeof message !== 'object') return false; - return 'type' in message && MESSAGE_TYPE_WHITELIST.includes(/** @type {InputMessageType} */ (message.type)); + return ( + 'type' in message && + MESSAGE_TYPE_WHITELIST.includes(/** @type {import('./types').InputMessageType} */ (message.type)) + ); }; export class MessageBridge { - /** @type {Map>>} */ + /** @type {Map>>} */ _handlerMap = new Map(); /** @type {Window} */ @@ -182,9 +47,9 @@ export class MessageBridge { }; /** - * @template {InputMessageType} T + * @template {import('./types').InputMessageType} T * @param {T} type - * @param {InputMessageHandler} handler + * @param {import('./types').InputMessageHandler} handler */ on(type, handler) { const handlers = this._handlerMap.get(type) ?? new Set(); @@ -192,10 +57,10 @@ export class MessageBridge { this._handlerMap.set(type, handlers); } - handlers.add(/** @type {InputMessageHandler} */ (handler)); + handlers.add(/** @type {import('./types').InputMessageHandler} */ (handler)); } - /** @param {OutputMessage} message */ + /** @param {import('./types').OutputMessage} message */ send(message) { this._context.postMessage(message, '*'); } diff --git a/blocks/ExternalSource/buildThemeDefinition.js b/blocks/ExternalSource/buildThemeDefinition.js index c8eb7682a..02ab3d149 100644 --- a/blocks/ExternalSource/buildThemeDefinition.js +++ b/blocks/ExternalSource/buildThemeDefinition.js @@ -9,7 +9,7 @@ function getCssValue(element, propName) { return style.getPropertyValue(propName).trim(); } -const ucCustomProperties = /** @type {(keyof import('./MessageBridge.js').ThemeDefinition)[]} */ ([ +const ucCustomProperties = /** @type {(keyof import('./types.js').ThemeDefinition)[]} */ ([ '--uc-font-family', '--uc-font-size', '--uc-line-height', @@ -75,5 +75,5 @@ export function buildThemeDefinition(element) { acc[prop] = value; } return acc; - }, /** @type {Record} */ ({})); + }, /** @type {Record} */ ({})); } diff --git a/blocks/ExternalSource/types.js b/blocks/ExternalSource/types.js new file mode 100644 index 000000000..3c28e3f90 --- /dev/null +++ b/blocks/ExternalSource/types.js @@ -0,0 +1,139 @@ +// @ts-check + +/** + * @typedef {{ + * caption?: string | null; + * created: number; + * id: string; + * public_page?: string; + * username: string; + * size?: number; + * name?: string; + * modified?: number; + * }} InstagramInfo + */ + +/** + * @typedef {{ + * error?: undefined; + * info?: InstagramInfo; + * alternatives?: Record; + * is_image?: boolean | null; + * filename?: string; + * obj_type: 'selected_file'; + * url: string; + * }} DoneSuccessResponse + */ + +/** + * @typedef {{ + * 'selected-files-change': { + * type: 'selected-files-change'; + * total: number; + * selectedCount: number; + * } & ( + * | { + * isReady: false; + * isMultipleMode: boolean; + * selectedFiles: undefined; + * } + * | { + * isReady: true; + * isMultipleMode: true; + * selectedFiles: DoneSuccessResponse[]; + * } + * | { + * isReady: true; + * isMultipleMode: false; + * selectedFiles: [DoneSuccessResponse] | []; + * } + * ); + * }} InputMessageMap + */ + +/** @typedef {keyof InputMessageMap} InputMessageType */ +/** @typedef {InputMessageMap[InputMessageType]} InputMessage */ + +/** + * @template {import('./types').InputMessageType} T + * @typedef {(message: import('./types').InputMessageMap[T]) => void} InputMessageHandler + */ + +/** + * @typedef {{ + * '--uc-font-family': string; + * '--uc-font-size': string; + * '--uc-line-height': string; + * '--uc-button-size': string; + * '--uc-preview-size': string; + * '--uc-input-size': string; + * '--uc-padding': string; + * '--uc-radius': string; + * '--uc-transition': string; + * '--uc-background': string; + * '--uc-foreground': string; + * '--uc-primary': string; + * '--uc-primary-hover': string; + * '--uc-primary-transparent': string; + * '--uc-primary-foreground': string; + * '--uc-secondary': string; + * '--uc-secondary-hover': string; + * '--uc-secondary-foreground': string; + * '--uc-muted': string; + * '--uc-muted-foreground': string; + * '--uc-destructive': string; + * '--uc-destructive-foreground': string; + * '--uc-border': string; + * '--uc-primary-rgb-light': string; + * '--uc-primary-light': string; + * '--uc-primary-hover-light': string; + * '--uc-primary-transparent-light': string; + * '--uc-background-light': string; + * '--uc-foreground-light': string; + * '--uc-primary-foreground-light': string; + * '--uc-secondary-light': string; + * '--uc-secondary-hover-light': string; + * '--uc-secondary-foreground-light': string; + * '--uc-muted-light': string; + * '--uc-muted-foreground-light': string; + * '--uc-destructive-light': string; + * '--uc-destructive-foreground-light': string; + * '--uc-border-light': string; + * '--uc-primary-rgb-dark': string; + * '--uc-primary-dark': string; + * '--uc-primary-hover-dark': string; + * '--uc-primary-transparent-dark': string; + * '--uc-background-dark': string; + * '--uc-foreground-dark': string; + * '--uc-primary-foreground-dark': string; + * '--uc-secondary-dark': string; + * '--uc-secondary-hover-dark': string; + * '--uc-secondary-foreground-dark': string; + * '--uc-muted-dark': string; + * '--uc-muted-foreground-dark': string; + * '--uc-destructive-dark': string; + * '--uc-destructive-foreground-dark': string; + * '--uc-border-dark': string; + * '--uc-primary-oklch-light': string; + * '--uc-primary-oklch-dark': string; + * }} ThemeDefinition + */ + +/** + * @typedef {{ + * type: 'select-all'; + * } + * | { + * type: 'deselect-all'; + * } + * | { + * type: 'set-theme-definition'; + * theme: Record; + * } + * | { + * type: 'set-embed-css'; + * css: Partial; + * }} OutputMessage + */ + +export {}; From dcf086893c130a71400fffcf4692a52359c69551 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Tue, 15 Oct 2024 13:37:14 +0300 Subject: [PATCH 03/12] chore: reduce theme variables list --- blocks/ExternalSource/buildThemeDefinition.js | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/blocks/ExternalSource/buildThemeDefinition.js b/blocks/ExternalSource/buildThemeDefinition.js index 02ab3d149..734770ad3 100644 --- a/blocks/ExternalSource/buildThemeDefinition.js +++ b/blocks/ExternalSource/buildThemeDefinition.js @@ -33,38 +33,6 @@ const ucCustomProperties = /** @type {(keyof import('./types.js').ThemeDefinitio '--uc-destructive', '--uc-destructive-foreground', '--uc-border', - '--uc-primary-rgb-light', - '--uc-primary-light', - '--uc-primary-hover-light', - '--uc-primary-transparent-light', - '--uc-background-light', - '--uc-foreground-light', - '--uc-primary-foreground-light', - '--uc-secondary-light', - '--uc-secondary-hover-light', - '--uc-secondary-foreground-light', - '--uc-muted-light', - '--uc-muted-foreground-light', - '--uc-destructive-light', - '--uc-destructive-foreground-light', - '--uc-border-light', - '--uc-primary-rgb-dark', - '--uc-primary-dark', - '--uc-primary-hover-dark', - '--uc-primary-transparent-dark', - '--uc-background-dark', - '--uc-foreground-dark', - '--uc-primary-foreground-dark', - '--uc-secondary-dark', - '--uc-secondary-hover-dark', - '--uc-secondary-foreground-dark', - '--uc-muted-dark', - '--uc-muted-foreground-dark', - '--uc-destructive-dark', - '--uc-destructive-foreground-dark', - '--uc-border-dark', - '--uc-primary-oklch-light', - '--uc-primary-oklch-dark', ]); /** @param {HTMLElement} element */ From 2af67dbef1cacd512e28e26e42d18a039b73fb4b Mon Sep 17 00:00:00 2001 From: nd0ut Date: Tue, 15 Oct 2024 13:44:55 +0300 Subject: [PATCH 04/12] chore: reset selection state on unmount --- blocks/ExternalSource/ExternalSource.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/blocks/ExternalSource/ExternalSource.js b/blocks/ExternalSource/ExternalSource.js index b595da465..5dde9c102 100644 --- a/blocks/ExternalSource/ExternalSource.js +++ b/blocks/ExternalSource/ExternalSource.js @@ -210,7 +210,7 @@ export class ExternalSource extends UploaderBlock { this._messageBridge = new MessageBridge(iframe.contentWindow); this._messageBridge.on('selected-files-change', this.handleSelectedFilesChange.bind(this)); - this.$.selectedList = []; + this.resetSelectionStatus(); } /** @private */ @@ -218,7 +218,20 @@ export class ExternalSource extends UploaderBlock { this._messageBridge?.destroy(); this._messageBridge = undefined; this.ref.iframeWrapper.innerHTML = ''; - this.$.selectedList = []; + + this.resetSelectionStatus(); + } + + /** @private */ + resetSelectionStatus() { + this.set$({ + selectedList: [], + total: 0, + isSelectionReady: false, + couldSelectAll: false, + couldDeselectAll: false, + showSelectionStatus: false, + }); } } From 79fc9fcfa4e2556503f659b82ec600d76a9e016a Mon Sep 17 00:00:00 2001 From: nd0ut Date: Wed, 23 Oct 2024 16:40:17 +0300 Subject: [PATCH 05/12] chore: add social sourcesdemo page --- demo/new-social-sources-test.html | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 demo/new-social-sources-test.html diff --git a/demo/new-social-sources-test.html b/demo/new-social-sources-test.html new file mode 100644 index 000000000..98b7690a9 --- /dev/null +++ b/demo/new-social-sources-test.html @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + +
+ Options + + + +
\ No newline at end of file From ed073981262c93a6e5a71b5784d67239a8c31d23 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Thu, 24 Oct 2024 17:50:14 +0300 Subject: [PATCH 06/12] chore: fix activity width --- blocks/ExternalSource/external-source.css | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/blocks/ExternalSource/external-source.css b/blocks/ExternalSource/external-source.css index 7d875c0fd..2f279a70b 100644 --- a/blocks/ExternalSource/external-source.css +++ b/blocks/ExternalSource/external-source.css @@ -9,10 +9,7 @@ uc-external-source { } [uc-modal] uc-external-source { - width: min( - calc(var(--uc-dialog-max-width) - var(--uc-padding) * 2), - calc(var(--uc-dialog-max-width) - var(--uc-padding) * 2) - ); + width: min(calc(var(--uc-dialog-max-width) - var(--uc-padding) * 2), calc(100vw - var(--uc-padding) * 2)); height: var(--modal-content-height-fill, 100%); max-height: var(--modal-max-content-height); } From 1c96ee36334c0ec7cda73afd5e2df97e8221fad1 Mon Sep 17 00:00:00 2001 From: nd0ut Date: Fri, 25 Oct 2024 11:07:21 +0300 Subject: [PATCH 07/12] chore: disable scale for social source demo --- demo/new-social-sources-test.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/new-social-sources-test.html b/demo/new-social-sources-test.html index 98b7690a9..3a3285749 100644 --- a/demo/new-social-sources-test.html +++ b/demo/new-social-sources-test.html @@ -2,7 +2,7 @@ - +