UBER-373: Fix blurry avatars and other images (#3353)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-06-06 14:33:32 +07:00 committed by GitHub
parent 4f2a079ab0
commit 6a7fe71447
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 133 additions and 27 deletions

View File

@ -1,6 +1,6 @@
import { getEmbeddedLabel } from '@hcengineering/platform'
import { getFileUrl } from '@hcengineering/presentation'
import { Action, Menu, getEventPositionElement, showPopup } from '@hcengineering/ui'
import { Action, IconSize, Menu, getEventPositionElement, getIconSize2x, showPopup } from '@hcengineering/ui'
import { Node, createNodeFromContent, mergeAttributes, nodeInputRule } from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state'
import plugin from '../plugin'
@ -88,6 +88,27 @@ export const ImageRef = Node.create<ImageOptions>({
HTMLAttributes
)
merged.src = getFileUrl(merged['file-id'], 'full')
let width: IconSize | undefined
switch (merged.width) {
case '32px':
width = 'small'
break
case '64px':
width = 'medium'
break
case '128px':
case '256px':
width = 'large'
break
case '512px':
width = 'x-large'
break
}
if (width !== undefined) {
merged.src = getFileUrl(merged['file-id'], width)
merged.srcset =
getFileUrl(merged['file-id'], width) + ' 1x,' + getFileUrl(merged['file-id'], getIconSize2x(width)) + ' 2x'
}
merged.class = 'textEditorImage'
return ['img', merged]
},

View File

@ -189,6 +189,7 @@ export type TooltipAlignment = 'top' | 'bottom' | 'left' | 'right'
export type VerticalAlignment = 'top' | 'bottom'
export type HorizontalAlignment = 'left' | 'right'
// Be aware to update front getResizeID() to properly store resized images.
export type IconSize =
| 'inline'
| 'tiny'
@ -202,6 +203,26 @@ export type IconSize =
| '2x-large'
| 'full'
export function getIconSize2x (size: IconSize): IconSize {
switch (size) {
case 'inline':
case 'tiny':
case 'x-small':
case 'small':
case 'card':
case 'smaller':
case 'medium':
return 'large'
case 'large':
return 'x-large'
case 'x-large':
return '2x-large'
case '2x-large':
case 'full':
return 'full'
}
}
export interface DateOrShift {
date?: number
shift?: number

View File

@ -16,7 +16,7 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import type { Attachment } from '@hcengineering/attachment'
import { showPopup, closeTooltip, Label } from '@hcengineering/ui'
import { showPopup, closeTooltip, Label, getIconSize2x } from '@hcengineering/ui'
import presentation, { PDFViewer, getFileUrl } from '@hcengineering/presentation'
import filesize from 'filesize'
@ -69,6 +69,18 @@
}
let download: HTMLAnchorElement
$: imgStyle = isImage(value.type)
? `background-image: url(${getFileUrl(value.file, 'large')});
background-image: -webkit-image-set(
${getFileUrl(value.file, 'large')} 1x,
${getFileUrl(value.file, getIconSize2x('large'))} 2x
);
background-image: image-set(
${getFileUrl(value.file, 'large')} 1x,
${getFileUrl(value.file, getIconSize2x('large'))} 2x
);`
: ''
</script>
<div class="flex-row-center attachment-container">
@ -83,7 +95,7 @@
class="flex-center icon"
class:svg={value.type === 'image/svg+xml'}
class:image={isImage(value.type)}
style:background-image={isImage(value.type) ? `url(${getFileUrl(value.file, 'large')})` : 'none'}
style={imgStyle}
>
{#if !isImage(value.type)}{iconLabel(value.name)}{/if}
</div> -->

View File

@ -16,7 +16,7 @@
<script lang="ts">
import type { Attachment } from '@hcengineering/attachment'
import { getFileUrl, PDFViewer } from '@hcengineering/presentation'
import { showPopup, closeTooltip } from '@hcengineering/ui'
import { showPopup, closeTooltip, getIconSize2x } from '@hcengineering/ui'
import { getType } from '../utils'
import AttachmentPresenter from './AttachmentPresenter.svelte'
import AttachmentActions from './AttachmentActions.svelte'
@ -47,7 +47,15 @@
dispatch('open', popupInfo.id)
}}
>
<img src={getFileUrl(value.file, 'large')} alt={value.name} />
<img
src={getFileUrl(value.file, 'large')}
srcset={`${getFileUrl(value.file, 'large', value.name)} 1x, ${getFileUrl(
value.file,
getIconSize2x('large'),
value.name
)} 2x`}
alt={value.name}
/>
<div class="actions conner">
<AttachmentActions attachment={value} {isSaved} />
</div>

View File

@ -40,13 +40,13 @@
export let size: IconSize
export let icon: Asset | AnySvelteComponent | undefined = undefined
let url: string | undefined
let url: string[] | undefined
let avatarProvider: AvatarProvider | undefined
async function update (size: IconSize, avatar?: string | null, direct?: Blob) {
if (direct !== undefined) {
getBlobURL(direct).then((blobURL) => {
url = blobURL
url = [blobURL]
avatarProvider = undefined
})
} else if (avatar) {
@ -69,14 +69,16 @@
$: update(size, avatar, direct)
let imageElement: HTMLImageElement | undefined = undefined
$: srcset = url?.slice(1)?.join(', ')
</script>
<div class="ava-{size} flex-center avatar-container" class:no-img={!url}>
{#if url}
{#if size === 'large' || size === 'x-large' || size === '2x-large'}
<img class="ava-{size} ava-blur" src={url} alt={''} bind:this={imageElement} />
<img class="ava-{size} ava-blur" src={url[0]} {srcset} alt={''} bind:this={imageElement} />
{/if}
<img class="ava-{size} ava-mask" src={url} alt={''} bind:this={imageElement} />
<img class="ava-{size} ava-mask" src={url[0]} {srcset} alt={''} bind:this={imageElement} />
{:else}
<Icon icon={icon ?? AvatarIcon} size={size === 'card' ? 'x-small' : size} />
{/if}

View File

@ -19,12 +19,22 @@ import { Class, Client, DocumentQuery, Ref, RelatedDocument, WithLookup } from '
import login from '@hcengineering/login'
import { IntlString, Resources, getResource } from '@hcengineering/platform'
import { MessageBox, ObjectSearchResult, getClient, getFileUrl } from '@hcengineering/presentation'
import { AnyComponent, AnySvelteComponent, TooltipAlignment, parseURL, showPopup } from '@hcengineering/ui'
import {
AnyComponent,
AnySvelteComponent,
IconSize,
TooltipAlignment,
getIconSize2x,
parseURL,
showPopup
} from '@hcengineering/ui'
import AccountArrayEditor from './components/AccountArrayEditor.svelte'
import AccountBox from './components/AccountBox.svelte'
import AssigneeBox from './components/AssigneeBox.svelte'
import Avatar from './components/Avatar.svelte'
import ChannelFilter from './components/ChannelFilter.svelte'
import ChannelPanel from './components/ChannelPanel.svelte'
import ChannelPresenter from './components/ChannelPresenter.svelte'
import Channels from './components/Channels.svelte'
import ChannelsDropdown from './components/ChannelsDropdown.svelte'
import ChannelsEditor from './components/ChannelsEditor.svelte'
@ -39,17 +49,21 @@ import ContactsTabs from './components/ContactsTabs.svelte'
import CreateEmployee from './components/CreateEmployee.svelte'
import CreateOrganization from './components/CreateOrganization.svelte'
import CreatePerson from './components/CreatePerson.svelte'
import DeleteConfirmationPopup from './components/DeleteConfirmationPopup.svelte'
import EditEmployee from './components/EditEmployee.svelte'
import EditMember from './components/EditMember.svelte'
import EditOrganization from './components/EditOrganization.svelte'
import EditPerson from './components/EditPerson.svelte'
import EditableAvatar from './components/EditableAvatar.svelte'
import EmployeeAccountFilterValuePresenter from './components/EmployeeAccountFilterValuePresenter.svelte'
import EmployeeAccountPresenter from './components/EmployeeAccountPresenter.svelte'
import EmployeeAccountRefPresenter from './components/EmployeeAccountRefPresenter.svelte'
import EmployeeArrayEditor from './components/EmployeeArrayEditor.svelte'
import EmployeeBox from './components/EmployeeBox.svelte'
import EmployeeBrowser from './components/EmployeeBrowser.svelte'
import EmployeeEditor from './components/EmployeeEditor.svelte'
import EmployeeFilter from './components/EmployeeFilter.svelte'
import EmployeeFilterValuePresenter from './components/EmployeeFilterValuePresenter.svelte'
import EmployeePresenter from './components/EmployeePresenter.svelte'
import EmployeeRefPresenter from './components/EmployeeRefPresenter.svelte'
import MemberPresenter from './components/MemberPresenter.svelte'
@ -61,24 +75,18 @@ import OrganizationPresenter from './components/OrganizationPresenter.svelte'
import PersonEditor from './components/PersonEditor.svelte'
import PersonPresenter from './components/PersonPresenter.svelte'
import PersonRefPresenter from './components/PersonRefPresenter.svelte'
import SelectAvatars from './components/SelectAvatars.svelte'
import SocialEditor from './components/SocialEditor.svelte'
import SpaceMembers from './components/SpaceMembers.svelte'
import UserBox from './components/UserBox.svelte'
import UserBoxItems from './components/UserBoxItems.svelte'
import UserBoxList from './components/UserBoxList.svelte'
import UserInfo from './components/UserInfo.svelte'
import UsersPopup from './components/UsersPopup.svelte'
import ActivityChannelMessage from './components/activity/ActivityChannelMessage.svelte'
import ActivityChannelPresenter from './components/activity/ActivityChannelPresenter.svelte'
import ExpandRightDouble from './components/icons/ExpandRightDouble.svelte'
import IconMembers from './components/icons/Members.svelte'
import ChannelPresenter from './components/ChannelPresenter.svelte'
import ChannelPanel from './components/ChannelPanel.svelte'
import ActivityChannelPresenter from './components/activity/ActivityChannelPresenter.svelte'
import SelectAvatars from './components/SelectAvatars.svelte'
import UserBoxItems from './components/UserBoxItems.svelte'
import EmployeeFilter from './components/EmployeeFilter.svelte'
import EmployeeFilterValuePresenter from './components/EmployeeFilterValuePresenter.svelte'
import EmployeeAccountFilterValuePresenter from './components/EmployeeAccountFilterValuePresenter.svelte'
import DeleteConfirmationPopup from './components/DeleteConfirmationPopup.svelte'
import contact from './plugin'
import {
@ -98,6 +106,7 @@ import {
resolveLocation
} from './utils'
export * from './utils'
export { employeeByIdStore, employeesStore } from './utils'
export {
Channels,
@ -254,8 +263,6 @@ export interface PersonLabelTooltip {
props?: any
}
export * from './utils'
export default async (): Promise<Resources> => ({
actionImpl: {
KickEmployee: kickEmployee,
@ -321,9 +328,19 @@ export default async (): Promise<Resources> => ({
) => await queryContact(contact.class.Organization, client, query, filter)
},
function: {
GetFileUrl: getFileUrl,
GetGravatarUrl: getGravatarUrl,
GetColorUrl: (uri: string) => uri,
GetFileUrl: (file: string, size: IconSize, fileName?: string) => {
return [
getFileUrl(file, size, fileName),
getFileUrl(file, size, fileName) + ' 1x',
getFileUrl(file, getIconSize2x(size), fileName) + ' 2x'
]
},
GetGravatarUrl: (file: string, size: IconSize, fileName?: string) => [
getGravatarUrl(file, size),
getGravatarUrl(file, size) + ' 1x',
getGravatarUrl(file, getIconSize2x(size)) + ' 2x'
],
GetColorUrl: (uri: string) => [uri],
EmployeeSort: employeeSort,
FilterChannelInResult: filterChannelInResult,
FilterChannelNinResult: filterChannelNinResult,

View File

@ -81,7 +81,7 @@ export enum AvatarType {
/**
* @public
*/
export type GetAvatarUrl = (uri: string, size: IconSize) => string
export type GetAvatarUrl = (uri: string, size: IconSize) => string[]
/**
* @public

View File

@ -54,7 +54,7 @@ export function getGravatarUrl (
case 'x-small':
case 'small':
case 'medium':
width = 64
width = 128
break
case 'large':
width = 256
@ -62,6 +62,12 @@ export function getGravatarUrl (
case 'x-large':
width = 512
break
case '2x-large':
width = 1024
break
case 'full':
width = 2048
break
}
return `https://gravatar.com/avatar/${gravatarId}?s=${width}&d=${placeholder}`
}

View File

@ -101,7 +101,7 @@
}
}
.back {
// background: url('../../img/back_signin.png');
background-image: url('../../img/login_back.png');
background-image: -webkit-image-set(
'../../img/login_back.avif' 1x,
'../../img/login_back_2x.avif' 2x,

View File

@ -491,6 +491,19 @@ export function start (
server.close()
}
}
// export type IconSize =
// | 'inline'
// | 'tiny'
// | 'card'
// | 'x-small'
// | 'smaller'
// | 'small'
// | 'medium'
// | 'large'
// | 'x-large'
// | '2x-large'
// | 'full'
async function getResizeID (
size: string,
uuid: string,
@ -502,7 +515,9 @@ async function getResizeID (
switch (size) {
case 'inline':
case 'tiny':
case 'card':
case 'x-small':
case 'smaller':
case 'small':
case 'medium':
width = 64
@ -513,6 +528,10 @@ async function getResizeID (
case 'x-large':
width = 512
break
case '2x-large':
size = '2x-large_v2'
width = 1024
break
}
let hasSmall = false
const sizeId = uuid + `%size%${width}`