Skip to content

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

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

Merged
merged 1 commit into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion packages/text-editor/src/components/imageExt.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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]
},
Expand Down
21 changes: 21 additions & 0 deletions packages/ui/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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">
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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>
Expand Down
10 changes: 6 additions & 4 deletions plugins/contact-resources/src/components/Avatar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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}
Expand Down
47 changes: 32 additions & 15 deletions plugins/contact-resources/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand All @@ -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 {
Expand All @@ -98,6 +106,7 @@ import {
resolveLocation
} from './utils'

export * from './utils'
export { employeeByIdStore, employeesStore } from './utils'
export {
Channels,
Expand Down Expand Up @@ -254,8 +263,6 @@ export interface PersonLabelTooltip {
props?: any
}

export * from './utils'

export default async (): Promise<Resources> => ({
actionImpl: {
KickEmployee: kickEmployee,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion plugins/contact/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion plugins/contact/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,20 @@ export function getGravatarUrl (
case 'x-small':
case 'small':
case 'medium':
width = 64
width = 128
break
case 'large':
width = 256
break
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}`
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/login-resources/src/components/LoginApp.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
19 changes: 19 additions & 0 deletions server/front/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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}`
Expand Down