From db836e0060ac29af67987288f1d488d1df711111 Mon Sep 17 00:00:00 2001 From: Aleksandr Grenishin Date: Wed, 4 Dec 2024 16:42:32 +0300 Subject: [PATCH] feat: social sources redesign (#750) * feat: new social sources * chore: extract types to separate file * chore: reduce theme variables list * chore: reset selection state on unmount * chore: add social sourcesdemo page * chore: fix activity width * chore: disable scale for social source demo * chore: increase modal size & refactor modal css & remove dialog api fallback * chore: fix upload list height on safari * chore: added l10n * chore: cleanup * chore(l10n): add missing keys for all the locales --------- Co-authored-by: nd0ut Co-authored-by: Egor Didenko --- blocks/CameraSource/CameraSource.js | 2 - blocks/CameraSource/camera-source.css | 19 +- blocks/CloudImageEditorActivity/index.css | 6 +- blocks/ExternalSource/ExternalSource.js | 195 +++++++------- blocks/ExternalSource/MessageBridge.js | 71 ++++++ blocks/ExternalSource/buildStyles.js | 133 ---------- blocks/ExternalSource/buildThemeDefinition.js | 47 ++++ blocks/ExternalSource/external-source.css | 59 +++-- blocks/ExternalSource/messages.js | 35 --- blocks/ExternalSource/types.js | 143 +++++++++++ blocks/Modal/Modal.js | 15 +- blocks/Modal/modal.css | 57 +---- blocks/StartFrom/start-from.css | 8 +- blocks/UploadList/upload-list.css | 12 +- blocks/UrlSource/url-source.css | 8 - blocks/themes/uc-basic/theme.css | 6 +- demo/new-social-sources-test.html | 83 ++++++ locales/file-uploader/ar.js | 2 + locales/file-uploader/az.js | 2 + locales/file-uploader/ca.js | 2 + locales/file-uploader/cs.js | 2 + locales/file-uploader/da.js | 2 + locales/file-uploader/de.js | 2 + locales/file-uploader/el.js | 2 + locales/file-uploader/en.js | 4 +- locales/file-uploader/es.js | 2 + locales/file-uploader/et.js | 2 + locales/file-uploader/fi.js | 238 +++++++++--------- locales/file-uploader/fr.js | 2 + locales/file-uploader/he.js | 2 + locales/file-uploader/hy.js | 2 + locales/file-uploader/is.js | 2 + locales/file-uploader/it.js | 2 + locales/file-uploader/ja.js | 2 + locales/file-uploader/ka.js | 2 + locales/file-uploader/kk.js | 2 + locales/file-uploader/ko.js | 2 + locales/file-uploader/lv.js | 2 + locales/file-uploader/nb.js | 2 + locales/file-uploader/nl.js | 2 + locales/file-uploader/pl.js | 2 + locales/file-uploader/pt.js | 2 + locales/file-uploader/ro.js | 2 + locales/file-uploader/ru.js | 2 + locales/file-uploader/sk.js | 2 + locales/file-uploader/sr.js | 2 + locales/file-uploader/sv.js | 2 + locales/file-uploader/tr.js | 2 + locales/file-uploader/uk.js | 2 + locales/file-uploader/vi.js | 2 + locales/file-uploader/zh-TW.js | 2 + locales/file-uploader/zh.js | 2 + 52 files changed, 686 insertions(+), 521 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 create mode 100644 blocks/ExternalSource/types.js create mode 100644 demo/new-social-sources-test.html diff --git a/blocks/CameraSource/CameraSource.js b/blocks/CameraSource/CameraSource.js index 54a39ba96..1f3d7bf0b 100644 --- a/blocks/CameraSource/CameraSource.js +++ b/blocks/CameraSource/CameraSource.js @@ -66,8 +66,6 @@ export class CameraSource extends UploaderBlock { * @param {'granted' | 'denied' | 'prompt'} state */ _setPermissionsState = debounce((state) => { - this.classList.toggle('uc-initialized', state === 'granted'); - if (state === 'granted') { this.set$({ videoHidden: false, diff --git a/blocks/CameraSource/camera-source.css b/blocks/CameraSource/camera-source.css index dde1fbef9..5b67fb348 100644 --- a/blocks/CameraSource/camera-source.css +++ b/blocks/CameraSource/camera-source.css @@ -10,21 +10,9 @@ uc-camera-source { border-radius: var(--uc-radius); } -[uc-modal] uc-camera-source { - width: min(calc(var(--uc-dialog-max-width) - var(--uc-padding) * 2), calc(100vw - var(--uc-padding) * 2)); - height: 100vh; - max-height: var(--modal-max-content-height); -} - -uc-camera-source.uc-initialized { - height: max-content; -} - -@media only screen and (max-width: 430px) { - uc-camera-source { - width: calc(100vw - var(--uc-padding) * 2); - height: var(--modal-content-height-fill, 100%); - } +[uc-modal] > dialog:has(uc-camera-source[active]) { + width: 100%; + height: 100%; } uc-camera-source video { @@ -52,6 +40,7 @@ uc-camera-source .uc-content { flex: 1; justify-content: center; width: 100%; + height: 100%; padding: var(--uc-padding); padding-top: 0; overflow: hidden; diff --git a/blocks/CloudImageEditorActivity/index.css b/blocks/CloudImageEditorActivity/index.css index 2c0ed9144..475b98f6c 100644 --- a/blocks/CloudImageEditorActivity/index.css +++ b/blocks/CloudImageEditorActivity/index.css @@ -7,7 +7,7 @@ uc-cloud-image-editor-activity { background-color: var(--uc-background); } -[uc-modal] uc-cloud-image-editor-activity { - 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%); +[uc-modal] > dialog:has(uc-cloud-image-editor-activity[active]) { + width: 100%; + height: 100%; } diff --git a/blocks/ExternalSource/ExternalSource.js b/blocks/ExternalSource/ExternalSource.js index 549fa3ce0..42c47e3f0 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('./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 @@ 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,25 @@ 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; + }); + + this.subConfigValue('localeName', (val) => { + this.setupL10n(); }); } /** * @private - * @param {SelectedFileMessage} message + * @param {NonNullable[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,81 +125,71 @@ export class ExternalSource extends UploaderBlock { } } - 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, + }), + ); + + 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 @@ 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 = []; + 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 */ ` - -
- - {{activityCaption}} -
-
-
{{counter}}
+
+ {{counterText}} + + +
diff --git a/blocks/ExternalSource/MessageBridge.js b/blocks/ExternalSource/MessageBridge.js new file mode 100644 index 000000000..6c3f304fd --- /dev/null +++ b/blocks/ExternalSource/MessageBridge.js @@ -0,0 +1,71 @@ +/** @type {import('./types').InputMessageType[]} */ +const MESSAGE_TYPE_WHITELIST = ['selected-files-change']; + +/** + * @param {unknown} message + * @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 {import('./types').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 {import('./types').InputMessageType} T + * @param {T} type + * @param {import('./types').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 {import('./types').InputMessageHandler} */ (handler)); + } + + /** @param {import('./types').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..734770ad3 --- /dev/null +++ b/blocks/ExternalSource/buildThemeDefinition.js @@ -0,0 +1,47 @@ +// @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('./types.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', +]); + +/** @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..ff470a605 100644 --- a/blocks/ExternalSource/external-source.css +++ b/blocks/ExternalSource/external-source.css @@ -5,15 +5,12 @@ uc-external-source { height: 100%; background-color: var(--uc-background); overflow: hidden; + position: relative; } -[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) - ); - height: var(--modal-content-height-fill, 100%); - max-height: var(--modal-max-content-height); +[uc-modal] > dialog:has(uc-external-source[active]) { + width: 100%; + height: 100%; } uc-external-source > .uc-content { @@ -23,13 +20,6 @@ uc-external-source > .uc-content { grid-template-rows: 1fr min-content; } -@media only screen and (max-width: 430px) { - uc-external-source { - width: calc(100vw - var(--uc-padding) * 2); - height: var(--modal-content-height-fill, 100%); - } -} - uc-external-source iframe { display: block; width: 100%; @@ -42,11 +32,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 +45,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/blocks/ExternalSource/types.js b/blocks/ExternalSource/types.js new file mode 100644 index 000000000..7d4915eae --- /dev/null +++ b/blocks/ExternalSource/types.js @@ -0,0 +1,143 @@ +// @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-locale-definition'; + * localeDefinition: string; + * } + * | { + * type: 'set-embed-css'; + * css: Partial; + * }} OutputMessage + */ + +export {}; diff --git a/blocks/Modal/Modal.js b/blocks/Modal/Modal.js index 193519d1a..68c83e67d 100644 --- a/blocks/Modal/Modal.js +++ b/blocks/Modal/Modal.js @@ -58,17 +58,10 @@ export class Modal extends Block { initCallback() { super.initCallback(); - if (typeof HTMLDialogElement === 'function') { - this.ref.dialog.addEventListener('close', this._handleDialogClose); - this.ref.dialog.addEventListener('mousedown', this._handleDialogMouseDown); - this.ref.dialog.addEventListener('mouseup', this._handleDialogMouseUp); - } else { - this.setAttribute('dialog-fallback', ''); - let backdrop = document.createElement('div'); - backdrop.className = 'uc-backdrop'; - this.appendChild(backdrop); - backdrop.addEventListener('click', this._handleBackdropClick); - } + + this.ref.dialog.addEventListener('close', this._handleDialogClose); + this.ref.dialog.addEventListener('mousedown', this._handleDialogMouseDown); + this.ref.dialog.addEventListener('mouseup', this._handleDialogMouseUp); this.sub('*modalActive', (modalActive) => { if (this.$.isOpen !== modalActive) { diff --git a/blocks/Modal/modal.css b/blocks/Modal/modal.css index f9fc87c94..e70466fdd 100644 --- a/blocks/Modal/modal.css +++ b/blocks/Modal/modal.css @@ -1,46 +1,3 @@ -:where([uc-modal]) { - --modal-max-content-height: calc( - var(--uploadcare-blocks-window-height, 100vh) - 4 * var(--uc-padding) - var(--uc-button-size) - ); - --modal-content-height-fill: var(--uploadcare-blocks-window-height, 100vh); -} - -:where([uc-modal])[dialog-fallback] { - --uc-z-max: 2147483647; - - position: fixed; - z-index: var(--uc-z-max); - display: flex; - align-items: center; - justify-content: center; - width: 100vw; - height: 100vh; - pointer-events: none; - inset: 0; -} - -:where([uc-modal])[dialog-fallback] dialog[open] { - z-index: var(--uc-z-max); - pointer-events: auto; -} - -:where([uc-modal])[dialog-fallback] dialog[open] + .uc-backdrop { - position: fixed; - top: 0px; - left: 0px; - z-index: calc(var(--uc-z-max) - 1); - align-items: center; - justify-content: center; - width: 100vw; - height: 100vh; - background-color: oklch(0 0 0 / 0.1); - pointer-events: auto; -} - -:where([uc-modal])[strokes][dialog-fallback] dialog[open] + .uc-backdrop { - background-image: var(--modal-backdrop-background-image); -} - @supports selector(dialog::backdrop) { :where([uc-modal]) > dialog::backdrop { /* backdrop don't inherit theme properties */ @@ -67,12 +24,10 @@ :where([uc-modal]) > dialog { display: flex; flex-direction: column; - - /* there was `fit-content` but it doesn't reduce width after activity change */ - width: max-content; - max-width: min(calc(100% - var(--uc-padding) * 2), calc(var(--uc-dialog-max-width) - var(--uc-padding) * 2)); + width: min(var(--uc-dialog-width), 100%); + max-width: min(calc(100% - var(--uc-padding) * 2), var(--uc-dialog-max-width)); min-height: var(--uc-button-size); - max-height: calc(var(--uc-dialog-max-height) - var(--uc-padding) * 2); + max-height: min(calc(100% - var(--uc-padding) * 2), var(--uc-dialog-max-height)); margin: auto; padding: 0; overflow: hidden; @@ -88,9 +43,3 @@ :where(.uc-contrast) :where([uc-modal]) > dialog { outline: 1px solid var(--uc-border); } - -@media only screen and (max-width: 430px), only screen and (max-height: 600px) { - :where([uc-modal]) > dialog > .uc-content { - height: var(--modal-max-content-height); - } -} diff --git a/blocks/StartFrom/start-from.css b/blocks/StartFrom/start-from.css index 685fbdfe2..32e39c836 100644 --- a/blocks/StartFrom/start-from.css +++ b/blocks/StartFrom/start-from.css @@ -13,12 +13,8 @@ uc-start-from .uc-content { background-color: var(--uc-background); } -[uc-modal] uc-start-from { - width: min( - calc(var(--uc-dialog-width) - var(--uc-padding) * 2), - calc(var(--uc-dialog-max-width) - var(--uc-padding) * 2), - calc(100vw - var(--uc-padding) * 2) - ); +[uc-modal] > dialog:has(uc-start-from[active]) { + width: var(--uc-dialog-width); } [uc-modal] uc-start-from uc-drop-area { diff --git a/blocks/UploadList/upload-list.css b/blocks/UploadList/upload-list.css index 32b950635..d11e58a61 100644 --- a/blocks/UploadList/upload-list.css +++ b/blocks/UploadList/upload-list.css @@ -2,22 +2,12 @@ uc-upload-list { display: flex; flex-direction: column; width: 100%; - height: 100%; + height: max-content; overflow: hidden; background-color: var(--uc-background); transition: opacity var(--uc-transition); } -[uc-modal] uc-upload-list { - width: min( - calc(var(--uc-dialog-width) - var(--uc-padding) * 2), - calc(var(--uc-dialog-max-width) - var(--uc-padding) * 2), - calc(100vw - var(--uc-padding) * 2) - ); - height: max-content; - max-height: var(--modal-max-content-height); -} - uc-upload-list .uc-no-files { height: 32px; padding: 20px; diff --git a/blocks/UrlSource/url-source.css b/blocks/UrlSource/url-source.css index 524543745..54bfa058a 100644 --- a/blocks/UrlSource/url-source.css +++ b/blocks/UrlSource/url-source.css @@ -3,14 +3,6 @@ uc-url-source { background-color: var(--uc-background); } -[uc-modal] uc-url-source { - width: min( - calc(var(--uc-dialog-width) - var(--uc-padding) * 2), - calc(var(--uc-dialog-max-width) - var(--uc-padding) * 2), - calc(100vw - var(--uc-padding) * 2) - ); -} - uc-url-source > .uc-content { display: grid; grid-gap: 4px; diff --git a/blocks/themes/uc-basic/theme.css b/blocks/themes/uc-basic/theme.css index 3823ec9f6..584578eda 100644 --- a/blocks/themes/uc-basic/theme.css +++ b/blocks/themes/uc-basic/theme.css @@ -13,8 +13,8 @@ --uc-radius: 8px; --uc-transition: 0.2s ease; --uc-dialog-width: 430px; - --uc-dialog-max-width: 800px; - --uc-dialog-max-height: 600px; + --uc-dialog-max-width: 920px; + --uc-dialog-max-height: 675px; --uc-simple-btn-padding: 7px 14px; /* Default colors, in case of media query failure */ @@ -276,4 +276,4 @@ --uc-background-dark: oklch(10% 0 0); --uc-foreground-dark: oklch(100% 0 0); -} \ No newline at end of file +} diff --git a/demo/new-social-sources-test.html b/demo/new-social-sources-test.html new file mode 100644 index 000000000..aba4f0ad4 --- /dev/null +++ b/demo/new-social-sources-test.html @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + +
+ Options + + + +
\ No newline at end of file diff --git a/locales/file-uploader/ar.js b/locales/file-uploader/ar.js index 495465574..cae5dcf9f 100644 --- a/locales/file-uploader/ar.js +++ b/locales/file-uploader/ar.js @@ -27,6 +27,8 @@ export default { 'confirm-your-action': 'تأكيد الإجراء', 'are-you-sure': 'هل أنت متأكد؟', 'selected-count': 'المختارة:', + 'select-all': 'اختر الكل', + 'deselect-all': 'إلغاء اختيار الكل', 'upload-error': 'خطأ في الرفع', 'validation-error': 'خطأ في التحقق', 'no-files': 'لم يتم اختيار ملفات', diff --git a/locales/file-uploader/az.js b/locales/file-uploader/az.js index 89030150d..5c022c638 100644 --- a/locales/file-uploader/az.js +++ b/locales/file-uploader/az.js @@ -27,6 +27,8 @@ export default { 'confirm-your-action': 'Əməliyyatınızı təsdiqləyin', 'are-you-sure': 'Əminsinizmi?', 'selected-count': 'Seçildi:', + 'select-all': 'Hamısını seç', + 'deselect-all': 'Hamısını ləğv et', 'upload-error': 'Yükləmə xətası', 'validation-error': 'Doğrulama xətası', 'no-files': 'Fayl seçilməyib', diff --git a/locales/file-uploader/ca.js b/locales/file-uploader/ca.js index 2a604486a..3b3e1f2a3 100644 --- a/locales/file-uploader/ca.js +++ b/locales/file-uploader/ca.js @@ -27,6 +27,8 @@ export default { 'confirm-your-action': "Confirma l'acció", 'are-you-sure': 'Estàs segur?', 'selected-count': 'Seleccionat:', + 'select-all': 'Seleccionar tot', + 'deselect-all': 'Deseleccionar tot', 'upload-error': 'Error de càrrega', 'validation-error': 'Error de validació', 'no-files': 'Cap fitxer seleccionat', diff --git a/locales/file-uploader/cs.js b/locales/file-uploader/cs.js index 4bb263b38..4451d4f99 100644 --- a/locales/file-uploader/cs.js +++ b/locales/file-uploader/cs.js @@ -27,6 +27,8 @@ export default { 'confirm-your-action': 'Potvrdit akci', 'are-you-sure': 'Jste si jistí?', 'selected-count': 'Vybráno:', + 'select-all': 'Vybrat vše', + 'deselect-all': 'Zrušit výběr všeho', 'upload-error': 'Chyba nahrávání', 'validation-error': 'Chyba validace', 'no-files': 'Nebyly vybrány žádné soubory', diff --git a/locales/file-uploader/da.js b/locales/file-uploader/da.js index d1c722426..a4156daf6 100644 --- a/locales/file-uploader/da.js +++ b/locales/file-uploader/da.js @@ -27,6 +27,8 @@ export default { 'confirm-your-action': 'Bekræft din handling', 'are-you-sure': 'Er du sikker?', 'selected-count': 'Valgt:', + 'select-all': 'Vælg alle', + 'deselect-all': 'Fravælg alle', 'upload-error': 'Fejl ved upload', 'validation-error': 'Valideringsfejl', 'no-files': 'Ingen filer valgt', diff --git a/locales/file-uploader/de.js b/locales/file-uploader/de.js index eff79caf4..55d85e0b6 100644 --- a/locales/file-uploader/de.js +++ b/locales/file-uploader/de.js @@ -27,6 +27,8 @@ export default { 'confirm-your-action': 'Aktion bestätigen', 'are-you-sure': 'Sind Sie sicher?', 'selected-count': 'Ausgewählt:', + 'select-all': 'Alles auswählen', + 'deselect-all': 'Alles abwählen', 'upload-error': 'Fehler beim Hochladen', 'validation-error': 'Validierungsfehler', 'no-files': 'Keine Dateien ausgewählt', diff --git a/locales/file-uploader/el.js b/locales/file-uploader/el.js index ccdbc55b5..6c166f418 100644 --- a/locales/file-uploader/el.js +++ b/locales/file-uploader/el.js @@ -27,6 +27,8 @@ export default { 'confirm-your-action': 'Επιβεβαίωση της ενέργειάς σας', 'are-you-sure': 'Είστε σίγουροι;', 'selected-count': 'Επιλεγμένο:', + 'select-all': 'Επιλογή όλων', + 'deselect-all': 'Αποεπιλογή όλων', 'upload-error': 'Σφάλμα μεταφόρτωσης', 'validation-error': 'Σφάλμα επικύρωσης', 'no-files': 'Δεν έχουν επιλεχθεί αρχεία', 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', diff --git a/locales/file-uploader/es.js b/locales/file-uploader/es.js index 156728dc3..a7dd9ba2a 100644 --- a/locales/file-uploader/es.js +++ b/locales/file-uploader/es.js @@ -27,6 +27,8 @@ export default { 'confirm-your-action': 'Confirmar tu acción', 'are-you-sure': '¿Estás seguro?', 'selected-count': 'Seleccionado:', + 'select-all': 'Seleccionar todo', + 'deselect-all': 'Deseleccionar todo', 'upload-error': 'Error de subida', 'validation-error': 'Error de validación', 'no-files': 'No se han seleccionado archivos', diff --git a/locales/file-uploader/et.js b/locales/file-uploader/et.js index eb2a4e4e9..72ac604a6 100644 --- a/locales/file-uploader/et.js +++ b/locales/file-uploader/et.js @@ -27,6 +27,8 @@ export default { 'confirm-your-action': 'Kinnita toiming', 'are-you-sure': 'Oled sa kindel?', 'selected-count': 'Valitud:', + 'select-all': 'Vali kõik', + 'deselect-all': 'Tühista kõikide valik', 'upload-error': 'Üleslaadimise viga', 'validation-error': 'Valideerimise viga', 'no-files': 'Faile ei ole valitud', diff --git a/locales/file-uploader/fi.js b/locales/file-uploader/fi.js index 24872f9ac..d6fa4b1f5 100644 --- a/locales/file-uploader/fi.js +++ b/locales/file-uploader/fi.js @@ -1,120 +1,122 @@ export default { - 'locale-id': 'fi', - 'social-source-lang': 'fi', - 'upload-file': 'Lataa tiedosto', - 'upload-files': 'Lataa tiedostoja', - 'choose-file': 'Valitse tiedosto', - 'choose-files': 'Valitse tiedostoja', - 'drop-files-here': 'Raahaa tiedostot tänne', - 'select-file-source': 'Valitse tiedoston lähde', - selected: 'Valittu', - upload: 'Lataa', - 'add-more': 'Lisää', - cancel: 'Peruuta', - 'start-from-cancel': 'Peruuta alkuun', - clear: 'Tyhjennä', - 'camera-shot': 'Kuva', - 'upload-url': 'Tuo', - 'upload-url-placeholder': 'Liitä linkki tähän', - 'edit-image': 'Muokkaa kuvaa', - 'edit-detail': 'Lisätiedot', - back: 'Takaisin', - done: 'Valmis', - ok: 'Ok', - 'remove-from-list': 'Poista', - no: 'Ei', - yes: 'Kyllä', - 'confirm-your-action': 'Vahvista toimintosi', - 'are-you-sure': 'Oletko varma?', - 'selected-count': 'Valitut:', - 'upload-error': 'Latausvirhe', - 'validation-error': 'Validointivirhe', - 'no-files': 'Ei tiedostoja valittu', - browse: 'Selaa', - 'not-uploaded-yet': 'Ei vielä ladattu...', - file__one: 'tiedosto', - file__other: 'tiedostot', - error__one: 'virhe', - error__other: 'virheitä', - 'header-uploading': 'Ladataan {{count}} {{plural:file(count)}}', - 'header-failed': '{{count}} {{plural:error(count)}}', - 'header-succeed': '{{count}} {{plural:file(count)}} ladattu', - 'header-total': '{{count}} {{plural:file(count)}} valittu', - 'src-type-local': 'Laiteesta', - 'src-type-from-url': 'Linkistä', - 'src-type-camera': 'Kamera', - 'src-type-draw': 'Piirrä', - 'src-type-facebook': 'Facebook', - 'src-type-dropbox': 'Dropbox', - 'src-type-gdrive': 'Google Drive', - 'src-type-gphotos': 'Google Kuvat', - 'src-type-instagram': 'Instagram', - 'src-type-flickr': 'Flickr', - 'src-type-vk': 'VK', - 'src-type-evernote': 'Evernote', - 'src-type-box': 'Box', - 'src-type-onedrive': 'Onedrive', - 'src-type-huddle': 'Huddle', - 'src-type-other': 'Muu', - 'caption-from-url': 'Tuo linkistä', - 'caption-camera': 'Kamera', - 'caption-draw': 'Piirrä', - 'caption-edit-file': 'Muokkaa tiedostoa', - 'file-no-name': 'Ei nimeä...', - 'toggle-fullscreen': 'Vaihda koko näyttöön', - 'toggle-guides': 'Opastus', - rotate: 'Käännä', - 'flip-vertical': 'Käännä pystysuunnassa', - 'flip-horizontal': 'Käännä vaakasuunnassa', - apply: 'Käytä', - brightness: 'Kirkkaus', - contrast: 'Kontrasti', - saturation: 'Saturaatio', - exposure: 'Valotusaika', - gamma: 'Gamma', - vibrance: 'Elinvoima', - warmth: 'Lämpö', - enhance: 'Paranna', - original: 'Alkuperäinen', - resize: 'Muuta kuvan kokoa', - crop: 'Rajaa', - 'select-color': 'Valitse väri', - text: 'Teksti', - draw: 'Piirrä', - 'cancel-edit': 'Peruuta muokkaus', - 'tab-view': 'Esikatselu', - 'tab-details': 'Lisätiedot', - 'file-name': 'Nimi', - 'file-size': 'Koko', - 'cdn-url': 'CDN URL', - 'file-size-unknown': 'Tuntematon', - 'camera-permissions-denied': 'Kameran käyttöoikeus evätty', - 'camera-permissions-prompt': 'Salli kameran käyttö', - 'camera-permissions-request': 'Pyydä käyttöoikeutta', - 'files-count-limit-error-title': 'Tiedostojen lukumäärän ylitys', - 'files-count-limit-error-too-few': + 'locale-id': 'fi', + 'social-source-lang': 'en', + 'upload-file': 'Lataa tiedosto', + 'upload-files': 'Lataa tiedostoja', + 'choose-file': 'Valitse tiedosto', + 'choose-files': 'Valitse tiedostoja', + 'drop-files-here': 'Raahaa tiedostot tänne', + 'select-file-source': 'Valitse tiedoston lähde', + selected: 'Valittu', + upload: 'Lataa', + 'add-more': 'Lisää', + cancel: 'Peruuta', + 'start-from-cancel': 'Peruuta alkuun', + clear: 'Tyhjennä', + 'camera-shot': 'Kuva', + 'upload-url': 'Tuo', + 'upload-url-placeholder': 'Liitä linkki tähän', + 'edit-image': 'Muokkaa kuvaa', + 'edit-detail': 'Lisätiedot', + back: 'Takaisin', + done: 'Valmis', + ok: 'Ok', + 'remove-from-list': 'Poista', + no: 'Ei', + yes: 'Kyllä', + 'confirm-your-action': 'Vahvista toimintosi', + 'are-you-sure': 'Oletko varma?', + 'selected-count': 'Valitut:', + 'upload-error': 'Latausvirhe', + 'validation-error': 'Validointivirhe', + 'no-files': 'Ei tiedostoja valittu', + browse: 'Selaa', + 'not-uploaded-yet': 'Ei vielä ladattu...', + file__one: 'tiedosto', + file__other: 'tiedostot', + error__one: 'virhe', + error__other: 'virheitä', + 'header-uploading': 'Ladataan {{count}} {{plural:file(count)}}', + 'header-failed': '{{count}} {{plural:error(count)}}', + 'header-succeed': '{{count}} {{plural:file(count)}} ladattu', + 'header-total': '{{count}} {{plural:file(count)}} valittu', + 'src-type-local': 'Laiteesta', + 'src-type-from-url': 'Linkistä', + 'src-type-camera': 'Kamera', + 'src-type-draw': 'Piirrä', + 'src-type-facebook': 'Facebook', + 'src-type-dropbox': 'Dropbox', + 'src-type-gdrive': 'Google Drive', + 'src-type-gphotos': 'Google Kuvat', + 'src-type-instagram': 'Instagram', + 'src-type-flickr': 'Flickr', + 'src-type-vk': 'VK', + 'src-type-evernote': 'Evernote', + 'src-type-box': 'Box', + 'src-type-onedrive': 'Onedrive', + 'src-type-huddle': 'Huddle', + 'src-type-other': 'Muu', + 'caption-from-url': 'Tuo linkistä', + 'caption-camera': 'Kamera', + 'caption-draw': 'Piirrä', + 'caption-edit-file': 'Muokkaa tiedostoa', + 'file-no-name': 'Ei nimeä...', + 'toggle-fullscreen': 'Vaihda koko näyttöön', + 'toggle-guides': 'Opastus', + rotate: 'Käännä', + 'flip-vertical': 'Käännä pystysuunnassa', + 'flip-horizontal': 'Käännä vaakasuunnassa', + apply: 'Käytä', + brightness: 'Kirkkaus', + contrast: 'Kontrasti', + saturation: 'Saturaatio', + exposure: 'Valotusaika', + gamma: 'Gamma', + vibrance: 'Elinvoima', + warmth: 'Lämpö', + enhance: 'Paranna', + original: 'Alkuperäinen', + resize: 'Muuta kuvan kokoa', + crop: 'Rajaa', + 'select-color': 'Valitse väri', + text: 'Teksti', + draw: 'Piirrä', + 'cancel-edit': 'Peruuta muokkaus', + 'tab-view': 'Esikatselu', + 'tab-details': 'Lisätiedot', + 'file-name': 'Nimi', + 'file-size': 'Koko', + 'cdn-url': 'CDN URL', + 'file-size-unknown': 'Tuntematon', + 'camera-permissions-denied': 'Kameran käyttöoikeus evätty', + 'camera-permissions-prompt': 'Salli kameran käyttö', + 'camera-permissions-request': 'Pyydä käyttöoikeutta', + 'files-count-limit-error-title': 'Tiedostojen lukumäärän ylitys', + 'files-count-limit-error-too-few': 'Olet valinnut {{total}} {{plural:file(total)}}. Vähintään {{min}} {{plural:file(min)}} vaaditaan.', - 'files-count-limit-error-too-many': 'Olet valinnut liian monta tiedostoa. {{max}} {{plural:file(max)}} on enimmäismäärä.', - 'files-max-size-limit-error': 'Tiedosto on liian suuri. Maksimi tiedostokoko on {{maxFileSize}}.', - 'has-validation-errors': 'Tiedostojen validointivirhe. Tarkista tiedostosi ennen latausta.', - 'images-only-accepted': 'Vain kuvamuotoiset tiedostot hyväksytään.', - 'file-type-not-allowed': 'Näiden tiedostotyyppien lataaminen ei ole sallittua.', - 'some-files-were-not-uploaded': 'Joitain tiedostoja ei ladattu.', - 'file-item-edit-button': 'Muokkaa', - 'file-item-remove-button': 'Poista', - 'a11y-editor-tab-filters': 'Suodattimet', - 'a11y-editor-tab-tuning': 'Säätö', - 'a11y-editor-tab-crop': 'Rajaus', - 'a11y-activity-header-button-close': 'Sulje', - flip: 'Käännä', - mirror: 'Peilaa', - 'a11y-cloud-editor-apply-filter': 'Käytä {{name}} suodatin', - 'a11y-cloud-editor-apply-crop': 'Käytä {{name}} rajaus', - 'a11y-cloud-editor-apply-tuning': 'Käytä {{name}} säätö', - finished: 'Valmis', - failed: 'Epäonnistui', - uploading: 'Ladataan', - idle: 'Toimeton', - 'a11y-file-item-status': 'Tiedosto {{fileName}} tilassa {{status}}', - }; - \ No newline at end of file + 'files-count-limit-error-too-many': + 'Olet valinnut liian monta tiedostoa. {{max}} {{plural:file(max)}} on enimmäismäärä.', + 'files-max-size-limit-error': 'Tiedosto on liian suuri. Maksimi tiedostokoko on {{maxFileSize}}.', + 'has-validation-errors': 'Tiedostojen validointivirhe. Tarkista tiedostosi ennen latausta.', + 'images-only-accepted': 'Vain kuvamuotoiset tiedostot hyväksytään.', + 'file-type-not-allowed': 'Näiden tiedostotyyppien lataaminen ei ole sallittua.', + 'some-files-were-not-uploaded': 'Joitain tiedostoja ei ladattu.', + 'file-item-edit-button': 'Muokkaa', + 'file-item-remove-button': 'Poista', + 'a11y-editor-tab-filters': 'Suodattimet', + 'a11y-editor-tab-tuning': 'Säätö', + 'a11y-editor-tab-crop': 'Rajaus', + 'a11y-activity-header-button-close': 'Sulje', + flip: 'Käännä', + mirror: 'Peilaa', + 'a11y-cloud-editor-apply-filter': 'Käytä {{name}} suodatin', + 'a11y-cloud-editor-apply-crop': 'Käytä {{name}} rajaus', + 'a11y-cloud-editor-apply-tuning': 'Käytä {{name}} säätö', + finished: 'Valmis', + failed: 'Epäonnistui', + uploading: 'Ladataan', + idle: 'Toimeton', + 'a11y-file-item-status': 'Tiedosto {{fileName}} tilassa {{status}}', + 'select-all': 'Valitse kaikki', + 'deselect-all': 'Poista valinnat', +}; diff --git a/locales/file-uploader/fr.js b/locales/file-uploader/fr.js index 8255e3bfe..cd3017cf9 100644 --- a/locales/file-uploader/fr.js +++ b/locales/file-uploader/fr.js @@ -119,4 +119,6 @@ export default { uploading: 'Téléchargement', idle: 'Inactif', 'a11y-file-item-status': 'Le fichier {{fileName}} est en statut {{status}}', + 'select-all': 'Tout sélectionner', + 'deselect-all': 'Tout désélectionner', }; diff --git a/locales/file-uploader/he.js b/locales/file-uploader/he.js index 844002ba5..f9cfaf578 100644 --- a/locales/file-uploader/he.js +++ b/locales/file-uploader/he.js @@ -118,4 +118,6 @@ export default { uploading: 'מעלה', idle: 'בטלה', 'a11y-file-item-status': 'הקובץ {{fileName}} במצב {{status}}', + 'select-all': 'בחר הכל', + 'deselect-all': 'בטל בחירת הכל', }; diff --git a/locales/file-uploader/hy.js b/locales/file-uploader/hy.js index a8ed15d53..6f5d29f82 100644 --- a/locales/file-uploader/hy.js +++ b/locales/file-uploader/hy.js @@ -116,4 +116,6 @@ export default { uploading: 'Բեռնվում է', idle: 'Անգործ', 'a11y-file-item-status': 'Ֆայլը {{fileName}} {{status}} կարգավիճակում է', + 'select-all': 'Ընտրել բոլորը', + 'deselect-all': 'Հրաժարվել բոլորից', }; diff --git a/locales/file-uploader/is.js b/locales/file-uploader/is.js index 314e26e1f..3c4549431 100644 --- a/locales/file-uploader/is.js +++ b/locales/file-uploader/is.js @@ -116,4 +116,6 @@ export default { uploading: 'Hleður upp', idle: 'Aðgerðalaus', 'a11y-file-item-status': 'Skráin {{fileName}} er í stöðu {{status}}', + 'select-all': 'Velja allt', + 'deselect-all': 'Afvelja allt', }; diff --git a/locales/file-uploader/it.js b/locales/file-uploader/it.js index 0e9d976ac..ae10a91d8 100644 --- a/locales/file-uploader/it.js +++ b/locales/file-uploader/it.js @@ -119,4 +119,6 @@ export default { uploading: 'Caricamento', idle: 'Inattivo', 'a11y-file-item-status': 'Il file {{fileName}} è nello stato {{status}}', + 'select-all': 'Seleziona tutto', + 'deselect-all': 'Deseleziona tutto', }; diff --git a/locales/file-uploader/ja.js b/locales/file-uploader/ja.js index e784aaf38..5d70bc56b 100644 --- a/locales/file-uploader/ja.js +++ b/locales/file-uploader/ja.js @@ -116,4 +116,6 @@ export default { uploading: 'アップロード中', idle: 'アイドル', 'a11y-file-item-status': 'ファイル {{fileName}} のステータスは {{status}} です', + 'select-all': 'すべて選択', + 'deselect-all': 'すべて選択解除', }; diff --git a/locales/file-uploader/ka.js b/locales/file-uploader/ka.js index 09ca2751a..3967bc749 100644 --- a/locales/file-uploader/ka.js +++ b/locales/file-uploader/ka.js @@ -116,4 +116,6 @@ export default { uploading: 'იტვირთება', idle: 'მოქმედების გარეშე', 'a11y-file-item-status': 'ფაილი {{fileName}} არის მდგომარეობაში {{status}}', + 'select-all': 'ყველას არჩევა', + 'deselect-all': 'ყველას არჩევის გაუქმება', }; diff --git a/locales/file-uploader/kk.js b/locales/file-uploader/kk.js index cc76f1562..cad2a875d 100644 --- a/locales/file-uploader/kk.js +++ b/locales/file-uploader/kk.js @@ -116,4 +116,6 @@ export default { uploading: 'Жүктелуде', idle: 'Бос', 'a11y-file-item-status': '{{fileName}} файлы {{status}} күйінде', + 'select-all': 'Барлығын таңдау', + 'deselect-all': 'Барлығын таңдаудан шығару', }; diff --git a/locales/file-uploader/ko.js b/locales/file-uploader/ko.js index ebef1c261..61e4fd512 100644 --- a/locales/file-uploader/ko.js +++ b/locales/file-uploader/ko.js @@ -115,4 +115,6 @@ export default { uploading: '업로드 중', idle: '대기 중', 'a11y-file-item-status': '{{fileName}} 파일이 {{status}} 상태입니다', + 'select-all': '모두 선택', + 'deselect-all': '모두 선택 해제', }; diff --git a/locales/file-uploader/lv.js b/locales/file-uploader/lv.js index d760e234c..3ce9d754d 100644 --- a/locales/file-uploader/lv.js +++ b/locales/file-uploader/lv.js @@ -118,4 +118,6 @@ export default { uploading: 'Augšupielādē', idle: 'Gaida', 'a11y-file-item-status': 'Fails {{fileName}} ir stāvoklī {{status}}', + 'select-all': 'Izvēlēties visus', + 'deselect-all': 'Noņemt izvēli visiem', }; diff --git a/locales/file-uploader/nb.js b/locales/file-uploader/nb.js index b33e48c37..b40793c11 100644 --- a/locales/file-uploader/nb.js +++ b/locales/file-uploader/nb.js @@ -116,4 +116,6 @@ export default { uploading: 'Laster opp', idle: 'Inaktiv', 'a11y-file-item-status': 'Fil {{fileName}} i status {{status}}', + 'select-all': 'Velg alle', + 'deselect-all': 'Fjern alle', }; diff --git a/locales/file-uploader/nl.js b/locales/file-uploader/nl.js index 8ce240248..3c710a286 100644 --- a/locales/file-uploader/nl.js +++ b/locales/file-uploader/nl.js @@ -117,4 +117,6 @@ export default { uploading: 'Uploaden', idle: 'Inactief', 'a11y-file-item-status': 'Bestand {{fileName}} in status {{status}}', + 'select-all': 'Selecteer alles', + 'deselect-all': 'Deselecteer alles', }; diff --git a/locales/file-uploader/pl.js b/locales/file-uploader/pl.js index 0eb170f46..5ef3e23a7 100644 --- a/locales/file-uploader/pl.js +++ b/locales/file-uploader/pl.js @@ -120,4 +120,6 @@ export default { uploading: 'Przesyłanie', idle: 'Bezczynny', 'a11y-file-item-status': 'Plik {{fileName}} ma status {{status}}', + 'select-all': 'Zaznacz wszystko', + 'deselect-all': 'Odznacz wszystko', }; diff --git a/locales/file-uploader/pt.js b/locales/file-uploader/pt.js index 585290bb5..7cf06c7ef 100644 --- a/locales/file-uploader/pt.js +++ b/locales/file-uploader/pt.js @@ -119,4 +119,6 @@ export default { uploading: 'Carregando', idle: 'Ocioso', 'a11y-file-item-status': 'O arquivo {{fileName}} está no status {{status}}', + 'select-all': 'Selecionar tudo', + 'deselect-all': 'Desmarcar tudo', }; diff --git a/locales/file-uploader/ro.js b/locales/file-uploader/ro.js index c9bc42a57..fa4cd9e23 100644 --- a/locales/file-uploader/ro.js +++ b/locales/file-uploader/ro.js @@ -119,4 +119,6 @@ export default { uploading: 'Se încarcă', idle: 'Inactiv', 'a11y-file-item-status': 'Fișierul {{fileName}} este în starea {{status}}', + 'select-all': 'Selectează tot', + 'deselect-all': 'Deselectează tot', }; diff --git a/locales/file-uploader/ru.js b/locales/file-uploader/ru.js index b7a35261d..7fc380448 100644 --- a/locales/file-uploader/ru.js +++ b/locales/file-uploader/ru.js @@ -120,4 +120,6 @@ export default { uploading: 'Загрузка', idle: 'Ожидание', 'a11y-file-item-status': 'Файл {{fileName}} находится в статусе {{status}}', + 'select-all': 'Выбрать все', + 'deselect-all': 'Отменить выбор всех', }; diff --git a/locales/file-uploader/sk.js b/locales/file-uploader/sk.js index c769aef58..c61703267 100644 --- a/locales/file-uploader/sk.js +++ b/locales/file-uploader/sk.js @@ -120,4 +120,6 @@ export default { uploading: 'Nahrávanie', idle: 'Nečinný', 'a11y-file-item-status': 'Súbor {{fileName}} je v stave {{status}}', + 'select-all': 'Vybrať všetko', + 'deselect-all': 'Zrušiť výber všetkého', }; diff --git a/locales/file-uploader/sr.js b/locales/file-uploader/sr.js index 50b2c318d..5e9dcdc6e 100644 --- a/locales/file-uploader/sr.js +++ b/locales/file-uploader/sr.js @@ -118,4 +118,6 @@ export default { uploading: 'Otpremanje', idle: 'Neaktivan', 'a11y-file-item-status': 'Datoteka {{fileName}} je u statusu {{status}}', + 'select-all': 'Изабери све', + 'deselect-all': 'Поништи избор свих', }; diff --git a/locales/file-uploader/sv.js b/locales/file-uploader/sv.js index f76de647a..c758a064c 100644 --- a/locales/file-uploader/sv.js +++ b/locales/file-uploader/sv.js @@ -116,4 +116,6 @@ export default { uploading: 'Laddar upp', idle: 'Overksam', 'a11y-file-item-status': 'Filen {{fileName}} är i status {{status}}', + 'select-all': 'Välj alla', + 'deselect-all': 'Avmarkera alla', }; diff --git a/locales/file-uploader/tr.js b/locales/file-uploader/tr.js index 9e306f24e..f7c7bd5c8 100644 --- a/locales/file-uploader/tr.js +++ b/locales/file-uploader/tr.js @@ -116,4 +116,6 @@ export default { uploading: 'Yükleniyor', idle: 'Boşta', 'a11y-file-item-status': '{{fileName}} dosyası {{status}} durumunda', + 'select-all': 'Hepsini seç', + 'deselect-all': 'Hiçbirini seçme', }; diff --git a/locales/file-uploader/uk.js b/locales/file-uploader/uk.js index 5185831a3..aa8cfb8ce 100644 --- a/locales/file-uploader/uk.js +++ b/locales/file-uploader/uk.js @@ -120,4 +120,6 @@ export default { uploading: 'Завантаження', idle: 'Очікування', 'a11y-file-item-status': 'Файл {{fileName}} у стані {{status}}', + 'select-all': 'Вибрати всі', + 'deselect-all': 'Скасувати вибір всіх', }; diff --git a/locales/file-uploader/vi.js b/locales/file-uploader/vi.js index 6e17a8449..f51059c6b 100644 --- a/locales/file-uploader/vi.js +++ b/locales/file-uploader/vi.js @@ -116,4 +116,6 @@ export default { uploading: 'Đang tải lên', idle: 'Nhàn rỗi', 'a11y-file-item-status': 'Tệp {{fileName}} ở trạng thái {{status}}', + 'select-all': 'Chọn tất cả', + 'deselect-all': 'Bỏ chọn tất cả', }; diff --git a/locales/file-uploader/zh-TW.js b/locales/file-uploader/zh-TW.js index 667822a69..4d43cce33 100644 --- a/locales/file-uploader/zh-TW.js +++ b/locales/file-uploader/zh-TW.js @@ -116,4 +116,6 @@ export default { uploading: '上傳中', idle: '閒置', 'a11y-file-item-status': '檔案 {{fileName}} 的狀態是 {{status}}', + 'select-all': '全選', + 'deselect-all': '取消全選', }; diff --git a/locales/file-uploader/zh.js b/locales/file-uploader/zh.js index 5638f77d5..f5cca75d4 100644 --- a/locales/file-uploader/zh.js +++ b/locales/file-uploader/zh.js @@ -116,4 +116,6 @@ export default { uploading: '上传中', idle: '空闲', 'a11y-file-item-status': '文件 {{fileName}} 的状态是 {{status}}', + 'select-all': '全选', + 'deselect-all': '取消全选', };