-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: social sources redesign #750
Changes from all commits
fdfda4c
f856136
dcf0868
2af67db
79fc9fc
ed07398
1c96ee3
d8e6555
3d45c44
6a1449a
9fdd697
bd9b7cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,31 +5,12 @@ | |
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<string, string>; | ||
* }} 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 @@ | |
...this.init$, | ||
activityIcon: '', | ||
activityCaption: '', | ||
|
||
/** @type {import('./types.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 @@ | |
onCancel: () => { | ||
this.historyBack(); | ||
}, | ||
|
||
onSelectAll: () => { | ||
this._messageBridge?.send({ type: 'select-all' }); | ||
}, | ||
|
||
onDeselectAll: () => { | ||
this._messageBridge?.send({ type: 'deselect-all' }); | ||
}, | ||
}; | ||
} | ||
|
||
|
@@ -69,12 +66,6 @@ | |
throw new Error(`External Source activity params not found`); | ||
} | ||
|
||
/** | ||
* @private | ||
* @type {HTMLIFrameElement | null} | ||
*/ | ||
_iframe = null; | ||
|
||
initCallback() { | ||
super.initCallback(); | ||
this.registerActivity(this.activityType, { | ||
|
@@ -96,7 +87,7 @@ | |
this.mountIframe(); | ||
}, | ||
}); | ||
this.sub('*currentActivityParams', (val) => { | ||
if (!this.isActivityActive) { | ||
return; | ||
} | ||
|
@@ -108,106 +99,97 @@ | |
this.unmountIframe(); | ||
} | ||
}); | ||
this.sub('selectedList', (list) => { | ||
this.$.counter = list.length; | ||
}); | ||
this.subConfigValue('multiple', (multiple) => { | ||
this.$.multiple = multiple; | ||
this.$.showSelectionStatus = multiple; | ||
}); | ||
|
||
this.subConfigValue('localeName', (val) => { | ||
this.setupL10n(); | ||
}); | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {SelectedFileMessage} message | ||
* @param {NonNullable<import('./types.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; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return message.url; | ||
return selectedFile.url; | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {Message} message | ||
* @param {import('./types.js').InputMessageMap['selected-files-change']} message | ||
*/ | ||
sendMessage(message) { | ||
this._iframe?.contentWindow?.postMessage(JSON.stringify(message), '*'); | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {SelectedFileMessage} 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, | ||
}), | ||
); | ||
Comment on lines
+141
to
+146
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid rebinding localization in Calling Move the binding to initCallback() {
super.initCallback();
+ this.bindL10n('counterText', () =>
+ this.l10n('selected-count', {
+ count: this.$.selectedCount,
+ total: this.$.total,
+ }),
+ );
// Existing code...
} Update this.set$({
isSelectionReady: message.isReady,
showSelectionStatus: message.isMultipleMode && message.total > 0,
couldSelectAll: message.selectedCount < message.total,
couldDeselectAll: message.selectedCount === message.total,
selectedList: message.selectedFiles,
+ selectedCount: 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 */ | ||
handleIframeLoad() { | ||
this.applyStyles(); | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {string} propName | ||
*/ | ||
getCssValue(propName) { | ||
let style = window.getComputedStyle(this); | ||
return style.getPropertyValue(propName).trim(); | ||
this.setupL10n(); | ||
} | ||
|
||
/** @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._messageBridge?.send({ | ||
type: 'set-theme-definition', | ||
theme: buildThemeDefinition(this), | ||
}); | ||
} | ||
|
||
this.sendMessage({ | ||
type: 'embed-css', | ||
style: buildStyles(colors), | ||
/** @private */ | ||
setupL10n() { | ||
this._messageBridge?.send({ | ||
type: 'set-locale-definition', | ||
localeDefinition: this.cfg.localeName, | ||
}); | ||
} | ||
|
||
/** @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 +213,43 @@ | |
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 = []; | ||
this.resetSelectionStatus(); | ||
} | ||
|
||
/** @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; | ||
|
||
this.resetSelectionStatus(); | ||
} | ||
|
||
/** @private */ | ||
resetSelectionStatus() { | ||
this.set$({ | ||
selectedList: [], | ||
total: 0, | ||
isSelectionReady: false, | ||
couldSelectAll: false, | ||
couldDeselectAll: false, | ||
showSelectionStatus: false, | ||
}); | ||
} | ||
} | ||
|
||
ExternalSource.template = /* HTML */ ` | ||
<uc-activity-header> | ||
<button type="button" class="uc-mini-btn" set="onclick: *historyBack" l10n="@title:back"> | ||
<uc-icon name="back"></uc-icon> | ||
</button> | ||
<div> | ||
<uc-icon set="@name: activityIcon"></uc-icon> | ||
<span>{{activityCaption}}</span> | ||
</div> | ||
<button | ||
type="button" | ||
class="uc-mini-btn uc-close-btn" | ||
|
@@ -269,12 +263,15 @@ | |
<div ref="iframeWrapper" class="uc-iframe-wrapper"></div> | ||
<div class="uc-toolbar"> | ||
<button type="button" class="uc-cancel-btn uc-secondary-btn" set="onclick: onCancel" l10n="cancel"></button> | ||
<div></div> | ||
<div set="@hidden: !multiple" class="uc-selected-counter"><span l10n="selected-count"></span>{{counter}}</div> | ||
<div set="@hidden: !showSelectionStatus" class="uc-selection-status-box"> | ||
<span>{{counterText}}</span> | ||
<button type="button" set="onclick: onSelectAll; @hidden: !couldSelectAll" l10n="select-all"></button> | ||
<button type="button" set="onclick: onDeselectAll; @hidden: !couldDeselectAll" l10n="deselect-all"></button> | ||
</div> | ||
<button | ||
type="button" | ||
class="uc-done-btn uc-primary-btn" | ||
set="onclick: onDone; @disabled: !counter" | ||
set="onclick: onDone; @disabled: !isSelectionReady" | ||
l10n="done" | ||
></button> | ||
</div> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
:has()
selector usage needs fallback stylesThe codebase search reveals that while there are default dialog styles in
blocks/Modal/modal.css
using:where([uc-modal]) > dialog
, these don't fully address the width/height requirements when specific components are active. The:has()
selector is used to apply full-width/height styles for certain components, but there's no fallback mechanism for browsers that don't support it.blocks/Modal/modal.css
provides base dialog styles with fixed widths:has()
supportRecommendation: Add fallback styles using a parent class or attribute that can be toggled via JavaScript when these components are active. For example:
🔗 Analysis chain
Potential browser compatibility issues with the
:has()
pseudo-classThe
:has()
CSS pseudo-class is not fully supported in all browsers, which may lead to layout issues for some users. Ensure that the target browsers support:has()
, or provide a fallback for those that do not.Run the following script to find all usages of
:has()
and verify browser compatibility::has()
selector usage requires fallback styles for older browsersBased on the search results, the
:has()
selector is used consistently across multiple CSS files in modal dialogs and file uploader components. This pattern affects core UI functionality and needs fallback styles to ensure compatibility with older browsers. Here are the affected files:blocks/CloudImageEditorActivity/index.css:10
blocks/StartFrom/start-from.css:16
blocks/ExternalSource/external-source.css:11
blocks/CameraSource/camera-source.css:13
solutions/file-uploader/inline/index.css
(multiple instances)Let's verify if there are any existing fallback styles:
Let me try one more search to check for any default dialog styles or JavaScript-based fallbacks:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 989
Script:
Length of output: 1136
Script:
Length of output: 4628