From ef987eef560befc9882b676a71c80a19b78463ae Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Tue, 25 Apr 2023 23:11:50 +0700 Subject: [PATCH] TSK-1323: Fix colors for list (#3069) Signed-off-by: Andrey Sobolev --- packages/ui/src/colors.ts | 165 +++++++++++++----- .../src/components/icons/CollapseArrow.svelte | 2 +- packages/ui/src/types.ts | 4 +- .../src/components/AssigneeBox.svelte | 2 +- .../src/components/Avatar.svelte | 36 +++- .../EmployeeAttributePresenter.svelte | 2 + .../src/components/EmployeePresenter.svelte | 1 + .../components/EmployeeRefPresenter.svelte | 22 ++- .../src/components/PersonContent.svelte | 4 +- .../src/components/PersonPresenter.svelte | 1 + .../src/components/UserInfo.svelte | 2 +- .../src/components/activity/StatusIcon.svelte | 2 +- .../src/components/icons/StatusIcon.svelte | 20 ++- .../components/issues/IssueStatusIcon.svelte | 7 +- .../components/issues/StatusPresenter.svelte | 2 +- .../issues/StatusRefPresenter.svelte | 1 + .../src/components/list/ListHeader.svelte | 114 +++++++----- 17 files changed, 275 insertions(+), 112 deletions(-) diff --git a/packages/ui/src/colors.ts b/packages/ui/src/colors.ts index fe1a07a043..e7a69d0e40 100644 --- a/packages/ui/src/colors.ts +++ b/packages/ui/src/colors.ts @@ -82,6 +82,27 @@ export function hexColorToNumber (hexColor: string): number { return parseInt(hexColor.replace('#', ''), 16) } +/** + * @public + */ +export function hexToRgb (color: string): { r: number, g: number, b: number } { + if (!color.startsWith('#')) { + return { r: 128, g: 128, b: 128 } + } + color = color.replace('#', '') + if (color.length === 3) { + color = color + .split('') + .map((c) => c + c) + .join('') + } + return { + r: parseInt(color.slice(0, 2), 16), + g: parseInt(color.slice(2, 4), 16), + b: parseInt(color.slice(4, 6), 16) + } +} + /** * @public */ @@ -110,54 +131,106 @@ export function numberToRGB (color: number, alpha?: number): string { /** * @public */ -export function hsvToRGB (h: number, s: number, v: number): { r: number, g: number, b: number, rgb: string } { +export function hslToRgb (h: number, s: number, l: number): { r: number, g: number, b: number } { + s /= 100 + l /= 100 + const k = (n: number): number => (n + h / 30) % 12 + const a = s * Math.min(l, 1 - l) + const f = (n: number): number => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))) + return { r: 255 * f(0), g: 255 * f(8), b: 255 * f(4) } +} + +/** + * @public + */ +export function rgbToHex (color: { r: number, g: number, b: number }): string { + function addZero (d: string): string { + if (d.length < 2) { + return '0' + d + } + return d + } + return ( + '#' + + addZero((Math.round(color.r) % 255).toString(16)) + + addZero((Math.round(color.g) % 255).toString(16)) + + addZero((Math.round(color.b) % 255).toString(16)) + ) +} + +export async function svgToColor (img: SVGSVGElement): Promise<{ r: number, g: number, b: number } | undefined> { + const outerHTML = img.outerHTML + const blob = new Blob([outerHTML], { type: 'image/svg+xml;charset=utf-8' }) + const blobURL = URL.createObjectURL(blob) + const image = new Image() + return await new Promise((resolve) => { + image.setAttribute('crossOrigin', '') + image.src = blobURL + image.onload = () => { + resolve(imageToColor(image)) + } + }) +} + +/** + * @public + */ +export function imageToColor (image: HTMLImageElement): { r: number, g: number, b: number } | undefined { + const canvas = document.createElement('canvas') + + const height = (canvas.height = image.naturalHeight ?? image.offsetHeight ?? image.height) + const width = (canvas.width = image.naturalWidth ?? image.offsetWidth ?? image.width) + canvas.width = width + canvas.height = height + + const blockSize = 5 + let r: number = 0 let g: number = 0 let b: number = 0 - const i = Math.floor(h * 6) - const f = h * 6 - i - const p = v * (1 - s) - const q = v * (1 - f * s) - const t = v * (1 - (1 - f) * s) - switch (i % 6) { - case 0: - r = v - g = t - b = p - break - case 1: - r = q - g = v - b = p - break - case 2: - r = p - g = v - b = t - break - case 3: - r = p - g = q - b = v - break - case 4: - r = t - g = p - b = v - break - case 5: - r = v - g = p - b = q - break - } - r = Math.round(r * 255) - g = Math.round(g * 255) - b = Math.round(b * 255) - return { - r, - g, - b, - rgb: '#' + r.toString(16) + g.toString(16) + b.toString(16) + let count = 0 + + const context = canvas.getContext('2d') + + if (context != null) { + context.drawImage(image, 0, 0, width, height) + context.beginPath() + context.arc(0, 0, 60, 0, Math.PI * 2, true) + context.clip() + context.fillRect(0, 0, width, height) + + const data = context?.getImageData(0, 0, width, height).data + + const length = data.length + + let i = 0 + while (i < length) { + if (data[i] > 5 && data[i + 1] > 5 && data[i + 2] > 5 && data[i + 3] > 50) { + ++count + r += data[i] + g += data[i + 1] + b += data[i + 2] + } + i += blockSize * 4 + } + + r = Math.round(r / count) + g = Math.round(g / count) + b = Math.round(b / count) + return { r, g, b } + } +} + +export function rgbToHsl (r: number, g: number, b: number): { h: number, s: number, l: number } { + r /= 255 + g /= 255 + b /= 255 + const l = Math.max(r, g, b) + const s = l - Math.min(r, g, b) + const h = s > 0 ? (l === r ? (g - b) / s : l === g ? 2 + (b - r) / s : 4 + (r - g) / s) : 0 + return { + h: 60 * h < 0 ? 60 * h + 360 : 60 * h, + s: 100 * (s > 0 ? (l <= 0.5 ? s / (2 * l - s) : s / (2 - (2 * l - s))) : 0), + l: (100 * (2 * l - s)) / 2 } } diff --git a/packages/ui/src/components/icons/CollapseArrow.svelte b/packages/ui/src/components/icons/CollapseArrow.svelte index 4ec4f10386..dd863e4a8d 100644 --- a/packages/ui/src/components/icons/CollapseArrow.svelte +++ b/packages/ui/src/components/icons/CollapseArrow.svelte @@ -1,6 +1,6 @@ diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 0eaa351d73..a47df328d4 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -15,7 +15,7 @@ import { Timestamp } from '@hcengineering/core' import type { Asset, IntlString } from '@hcengineering/platform' import { /* Metadata, Plugin, plugin, */ Resource /*, Service */ } from '@hcengineering/platform' -import { /* getContext, */ SvelteComponent } from 'svelte' +import { /* getContext, */ ComponentType } from 'svelte' /** * Describe a browser URI location parsed to path, query and fragment. @@ -55,7 +55,7 @@ export function areLocationsEqual (loc1: Location, loc2: Location): boolean { return keys1.findIndex((k) => loc1.query?.[k] !== loc2.query?.[k]) < 0 } -export type AnySvelteComponent = typeof SvelteComponent +export type AnySvelteComponent = ComponentType export type Component = Resource export type AnyComponent = Resource diff --git a/plugins/contact-resources/src/components/AssigneeBox.svelte b/plugins/contact-resources/src/components/AssigneeBox.svelte index 740e6e34e7..c27f686f90 100644 --- a/plugins/contact-resources/src/components/AssigneeBox.svelte +++ b/plugins/contact-resources/src/components/AssigneeBox.svelte @@ -145,7 +145,7 @@ > {#if selected} {#if hideIcon || selected} - + {:else} {getName(selected)} {/if} diff --git a/plugins/contact-resources/src/components/Avatar.svelte b/plugins/contact-resources/src/components/Avatar.svelte index bc907c2287..873c657a7a 100644 --- a/plugins/contact-resources/src/components/Avatar.svelte +++ b/plugins/contact-resources/src/components/Avatar.svelte @@ -31,7 +31,8 @@ import { Client, Ref } from '@hcengineering/core' import { Asset, getResource } from '@hcengineering/platform' import { getBlobURL, getClient } from '@hcengineering/presentation' - import { AnySvelteComponent, Icon, IconSize } from '@hcengineering/ui' + import { AnySvelteComponent, Icon, IconSize, hexToRgb, imageToColor } from '@hcengineering/ui' + import { createEventDispatcher } from 'svelte' import { getAvatarProviderId } from '../utils' import AvatarIcon from './icons/Avatar.svelte' @@ -43,6 +44,8 @@ let url: string | undefined let avatarProvider: AvatarProvider | undefined + const dispatch = createEventDispatcher() + async function update (size: IconSize, avatar?: string | null, direct?: Blob) { if (direct !== undefined) { getBlobURL(direct).then((blobURL) => { @@ -78,17 +81,44 @@ const color = (await getResource(avatarProvider.getUrl))(uri, size) style = `background-color: ${color}` + accentColor = hexToRgb(color) + dispatch('accent-color', accentColor) } } $: updateStyle(avatar, avatarProvider) + + let imageElement: HTMLImageElement | undefined = undefined + let accentColor: any | undefined
{#if url} {#if size === 'large' || size === 'x-large'} - {''} + {''} { + if (imageElement !== undefined) { + accentColor = imageToColor(imageElement) + dispatch('accent-color', accentColor) + } + }} + /> {/if} - {''} + {''} { + if (imageElement !== undefined) { + accentColor = imageToColor(imageElement) + dispatch('accent-color', accentColor) + } + }} + /> {:else} {/if} diff --git a/plugins/contact-resources/src/components/EmployeeAttributePresenter.svelte b/plugins/contact-resources/src/components/EmployeeAttributePresenter.svelte index 2a4f6fc192..73fae8b73c 100644 --- a/plugins/contact-resources/src/components/EmployeeAttributePresenter.svelte +++ b/plugins/contact-resources/src/components/EmployeeAttributePresenter.svelte @@ -38,6 +38,7 @@ showNavigate={false} justify={'left'} on:change={({ detail }) => onChange?.(detail)} + on:accent-color /> {:else} {/if} diff --git a/plugins/contact-resources/src/components/EmployeePresenter.svelte b/plugins/contact-resources/src/components/EmployeePresenter.svelte index 0bd7ad8dd4..1d5948e9cc 100644 --- a/plugins/contact-resources/src/components/EmployeePresenter.svelte +++ b/plugins/contact-resources/src/components/EmployeePresenter.svelte @@ -38,4 +38,5 @@ {accent} {defaultName} statusLabel={value?.active === false && shouldShowName ? contact.string.Inactive : undefined} + on:accent-color /> diff --git a/plugins/contact-resources/src/components/EmployeeRefPresenter.svelte b/plugins/contact-resources/src/components/EmployeeRefPresenter.svelte index 4f5c4be879..519dff0fe3 100644 --- a/plugins/contact-resources/src/components/EmployeeRefPresenter.svelte +++ b/plugins/contact-resources/src/components/EmployeeRefPresenter.svelte @@ -17,11 +17,29 @@ {#if Array.isArray(value)}
{#each value as employee} - + {/each}
{:else} - + {/if}