mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-24 01:07:50 +00:00
TSK-1323: Fix colors for list (#3069)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
123eb6e3ad
commit
ef987eef56
@ -82,6 +82,27 @@ export function hexColorToNumber (hexColor: string): number {
|
|||||||
return parseInt(hexColor.replace('#', ''), 16)
|
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
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -110,54 +131,106 @@ export function numberToRGB (color: number, alpha?: number): string {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @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 r: number = 0
|
||||||
let g: number = 0
|
let g: number = 0
|
||||||
let b: number = 0
|
let b: number = 0
|
||||||
const i = Math.floor(h * 6)
|
let count = 0
|
||||||
const f = h * 6 - i
|
|
||||||
const p = v * (1 - s)
|
const context = canvas.getContext('2d')
|
||||||
const q = v * (1 - f * s)
|
|
||||||
const t = v * (1 - (1 - f) * s)
|
if (context != null) {
|
||||||
switch (i % 6) {
|
context.drawImage(image, 0, 0, width, height)
|
||||||
case 0:
|
context.beginPath()
|
||||||
r = v
|
context.arc(0, 0, 60, 0, Math.PI * 2, true)
|
||||||
g = t
|
context.clip()
|
||||||
b = p
|
context.fillRect(0, 0, width, height)
|
||||||
break
|
|
||||||
case 1:
|
const data = context?.getImageData(0, 0, width, height).data
|
||||||
r = q
|
|
||||||
g = v
|
const length = data.length
|
||||||
b = p
|
|
||||||
break
|
let i = 0
|
||||||
case 2:
|
while (i < length) {
|
||||||
r = p
|
if (data[i] > 5 && data[i + 1] > 5 && data[i + 2] > 5 && data[i + 3] > 50) {
|
||||||
g = v
|
++count
|
||||||
b = t
|
r += data[i]
|
||||||
break
|
g += data[i + 1]
|
||||||
case 3:
|
b += data[i + 2]
|
||||||
r = p
|
}
|
||||||
g = q
|
i += blockSize * 4
|
||||||
b = v
|
}
|
||||||
break
|
|
||||||
case 4:
|
r = Math.round(r / count)
|
||||||
r = t
|
g = Math.round(g / count)
|
||||||
g = p
|
b = Math.round(b / count)
|
||||||
b = v
|
return { r, g, b }
|
||||||
break
|
}
|
||||||
case 5:
|
}
|
||||||
r = v
|
|
||||||
g = p
|
export function rgbToHsl (r: number, g: number, b: number): { h: number, s: number, l: number } {
|
||||||
b = q
|
r /= 255
|
||||||
break
|
g /= 255
|
||||||
}
|
b /= 255
|
||||||
r = Math.round(r * 255)
|
const l = Math.max(r, g, b)
|
||||||
g = Math.round(g * 255)
|
const s = l - Math.min(r, g, b)
|
||||||
b = Math.round(b * 255)
|
const h = s > 0 ? (l === r ? (g - b) / s : l === g ? 2 + (b - r) / s : 4 + (r - g) / s) : 0
|
||||||
return {
|
return {
|
||||||
r,
|
h: 60 * h < 0 ? 60 * h + 360 : 60 * h,
|
||||||
g,
|
s: 100 * (s > 0 ? (l <= 0.5 ? s / (2 * l - s) : s / (2 - (2 * l - s))) : 0),
|
||||||
b,
|
l: (100 * (2 * l - s)) / 2
|
||||||
rgb: '#' + r.toString(16) + g.toString(16) + b.toString(16)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let size: 'small' | 'medium' | 'large'
|
export let size: 'small' | 'medium' | 'large'
|
||||||
const fill: string = 'currentColor'
|
export let fill: string = 'currentColor'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
import { Timestamp } from '@hcengineering/core'
|
import { Timestamp } from '@hcengineering/core'
|
||||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||||
import { /* Metadata, Plugin, plugin, */ Resource /*, Service */ } 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.
|
* 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
|
return keys1.findIndex((k) => loc1.query?.[k] !== loc2.query?.[k]) < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnySvelteComponent = typeof SvelteComponent
|
export type AnySvelteComponent = ComponentType
|
||||||
export type Component<C extends AnySvelteComponent> = Resource<C>
|
export type Component<C extends AnySvelteComponent> = Resource<C>
|
||||||
export type AnyComponent = Resource<AnySvelteComponent>
|
export type AnyComponent = Resource<AnySvelteComponent>
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@
|
|||||||
>
|
>
|
||||||
{#if selected}
|
{#if selected}
|
||||||
{#if hideIcon || selected}
|
{#if hideIcon || selected}
|
||||||
<UserInfo value={selected} size={kind === 'link' ? 'x-small' : 'tiny'} {icon} />
|
<UserInfo value={selected} size={kind === 'link' ? 'x-small' : 'tiny'} {icon} on:accent-color />
|
||||||
{:else}
|
{:else}
|
||||||
{getName(selected)}
|
{getName(selected)}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -31,7 +31,8 @@
|
|||||||
import { Client, Ref } from '@hcengineering/core'
|
import { Client, Ref } from '@hcengineering/core'
|
||||||
import { Asset, getResource } from '@hcengineering/platform'
|
import { Asset, getResource } from '@hcengineering/platform'
|
||||||
import { getBlobURL, getClient } from '@hcengineering/presentation'
|
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 { getAvatarProviderId } from '../utils'
|
||||||
import AvatarIcon from './icons/Avatar.svelte'
|
import AvatarIcon from './icons/Avatar.svelte'
|
||||||
|
|
||||||
@ -43,6 +44,8 @@
|
|||||||
let url: string | undefined
|
let url: string | undefined
|
||||||
let avatarProvider: AvatarProvider | undefined
|
let avatarProvider: AvatarProvider | undefined
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
async function update (size: IconSize, avatar?: string | null, direct?: Blob) {
|
async function update (size: IconSize, avatar?: string | null, direct?: Blob) {
|
||||||
if (direct !== undefined) {
|
if (direct !== undefined) {
|
||||||
getBlobURL(direct).then((blobURL) => {
|
getBlobURL(direct).then((blobURL) => {
|
||||||
@ -78,17 +81,44 @@
|
|||||||
|
|
||||||
const color = (await getResource(avatarProvider.getUrl))(uri, size)
|
const color = (await getResource(avatarProvider.getUrl))(uri, size)
|
||||||
style = `background-color: ${color}`
|
style = `background-color: ${color}`
|
||||||
|
accentColor = hexToRgb(color)
|
||||||
|
dispatch('accent-color', accentColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$: updateStyle(avatar, avatarProvider)
|
$: updateStyle(avatar, avatarProvider)
|
||||||
|
|
||||||
|
let imageElement: HTMLImageElement | undefined = undefined
|
||||||
|
let accentColor: any | undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="ava-{size} flex-center avatar-container" class:no-img={!url} {style}>
|
<div class="ava-{size} flex-center avatar-container" class:no-img={!url} {style}>
|
||||||
{#if url}
|
{#if url}
|
||||||
{#if size === 'large' || size === 'x-large'}
|
{#if size === 'large' || size === 'x-large'}
|
||||||
<img class="ava-{size} ava-blur" src={url} alt={''} />
|
<img
|
||||||
|
class="ava-{size} ava-blur"
|
||||||
|
src={url}
|
||||||
|
alt={''}
|
||||||
|
bind:this={imageElement}
|
||||||
|
on:load={(data) => {
|
||||||
|
if (imageElement !== undefined) {
|
||||||
|
accentColor = imageToColor(imageElement)
|
||||||
|
dispatch('accent-color', accentColor)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<img class="ava-{size} ava-mask" src={url} alt={''} />
|
<img
|
||||||
|
class="ava-{size} ava-mask"
|
||||||
|
src={url}
|
||||||
|
alt={''}
|
||||||
|
bind:this={imageElement}
|
||||||
|
on:load={(data) => {
|
||||||
|
if (imageElement !== undefined) {
|
||||||
|
accentColor = imageToColor(imageElement)
|
||||||
|
dispatch('accent-color', accentColor)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon={icon ?? AvatarIcon} {size} />
|
<Icon icon={icon ?? AvatarIcon} {size} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
showNavigate={false}
|
showNavigate={false}
|
||||||
justify={'left'}
|
justify={'left'}
|
||||||
on:change={({ detail }) => onChange?.(detail)}
|
on:change={({ detail }) => onChange?.(detail)}
|
||||||
|
on:accent-color
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<EmployeePresenter
|
<EmployeePresenter
|
||||||
@ -53,5 +54,6 @@
|
|||||||
disableClick
|
disableClick
|
||||||
{colorInherit}
|
{colorInherit}
|
||||||
{accent}
|
{accent}
|
||||||
|
on:accent-color
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -38,4 +38,5 @@
|
|||||||
{accent}
|
{accent}
|
||||||
{defaultName}
|
{defaultName}
|
||||||
statusLabel={value?.active === false && shouldShowName ? contact.string.Inactive : undefined}
|
statusLabel={value?.active === false && shouldShowName ? contact.string.Inactive : undefined}
|
||||||
|
on:accent-color
|
||||||
/>
|
/>
|
||||||
|
@ -17,11 +17,29 @@
|
|||||||
{#if Array.isArray(value)}
|
{#if Array.isArray(value)}
|
||||||
<div class="inline-content">
|
<div class="inline-content">
|
||||||
{#each value as employee}
|
{#each value as employee}
|
||||||
<EmployeeAttributePresenter value={employee} {kind} {tooltipLabels} {onChange} {inline} {colorInherit} {accent} />
|
<EmployeeAttributePresenter
|
||||||
|
value={employee}
|
||||||
|
{kind}
|
||||||
|
{tooltipLabels}
|
||||||
|
{onChange}
|
||||||
|
{inline}
|
||||||
|
{colorInherit}
|
||||||
|
{accent}
|
||||||
|
on:accent-color
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<EmployeeAttributePresenter {value} {kind} {tooltipLabels} {onChange} {inline} {colorInherit} {accent} />
|
<EmployeeAttributePresenter
|
||||||
|
{value}
|
||||||
|
{kind}
|
||||||
|
{tooltipLabels}
|
||||||
|
{onChange}
|
||||||
|
{inline}
|
||||||
|
{colorInherit}
|
||||||
|
{accent}
|
||||||
|
on:accent-color
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
class:mr-2={shouldShowName && !enlargedText}
|
class:mr-2={shouldShowName && !enlargedText}
|
||||||
class:mr-3={shouldShowName && enlargedText}
|
class:mr-3={shouldShowName && enlargedText}
|
||||||
>
|
>
|
||||||
<Avatar size={avatarSize} avatar={value.avatar} />
|
<Avatar size={avatarSize} avatar={value.avatar} on:accent-color />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if shouldShowName}
|
{#if shouldShowName}
|
||||||
@ -79,7 +79,7 @@
|
|||||||
class:mr-2={shouldShowName && !enlargedText}
|
class:mr-2={shouldShowName && !enlargedText}
|
||||||
class:mr-3={shouldShowName && enlargedText}
|
class:mr-3={shouldShowName && enlargedText}
|
||||||
>
|
>
|
||||||
<Avatar size={avatarSize} />
|
<Avatar size={avatarSize} on:accent-color />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if shouldShowName && defaultName}
|
{#if shouldShowName && defaultName}
|
||||||
|
@ -78,5 +78,6 @@
|
|||||||
{colorInherit}
|
{colorInherit}
|
||||||
{accent}
|
{accent}
|
||||||
bind:element
|
bind:element
|
||||||
|
on:accent-color
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="flex-row-center" on:click>
|
<div class="flex-row-center" on:click>
|
||||||
<Avatar avatar={value.avatar} {size} {icon} />
|
<Avatar avatar={value.avatar} {size} {icon} on:accent-color />
|
||||||
<div class="flex-col min-w-0 {size === 'tiny' || size === 'inline' ? 'ml-1' : 'ml-2'}">
|
<div class="flex-col min-w-0 {size === 'tiny' || size === 'inline' ? 'ml-1' : 'ml-2'}">
|
||||||
{#if subtitle}<div class="content-dark-color text-sm">{subtitle}</div>{/if}
|
{#if subtitle}<div class="content-dark-color text-sm">{subtitle}</div>{/if}
|
||||||
<div class="content-accent-color overflow-label text-left">{getName(value)}</div>
|
<div class="content-accent-color overflow-label text-left">{getName(value)}</div>
|
||||||
|
@ -26,6 +26,6 @@
|
|||||||
|
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
{#if status}
|
{#if status}
|
||||||
<IssueStatusIcon value={status} size="small" />
|
<IssueStatusIcon value={status} size="small" on:accent-color />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { StatusCategory } from '@hcengineering/core'
|
import { StatusCategory } from '@hcengineering/core'
|
||||||
import { IconSize } from '@hcengineering/ui'
|
import { IconSize, hexToRgb } from '@hcengineering/ui'
|
||||||
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
export let size: IconSize
|
export let size: IconSize
|
||||||
export let fill: string = 'currentColor'
|
const defaultFill = 'currentColor'
|
||||||
|
export let fill: string = defaultFill
|
||||||
export let category: StatusCategory
|
export let category: StatusCategory
|
||||||
export let statusIcon: {
|
export let statusIcon: {
|
||||||
index: number | undefined
|
index: number | undefined
|
||||||
count: number | undefined
|
count: number | undefined
|
||||||
} = { index: 0, count: 0 }
|
} = { index: 0, count: 0 }
|
||||||
|
|
||||||
|
let element: SVGSVGElement
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
const dispatchAccentColor = (fill: string) =>
|
||||||
|
dispatch('accent-color', fill !== defaultFill ? hexToRgb(fill) : 'var(--theme-halfcontent-color)')
|
||||||
|
|
||||||
|
$: dispatchAccentColor(fill)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
dispatchAccentColor(fill)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
|
bind:this={element}
|
||||||
class="svg-{size}"
|
class="svg-{size}"
|
||||||
{fill}
|
{fill}
|
||||||
id={category._id}
|
id={category._id}
|
||||||
|
@ -14,11 +14,10 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import core, { StatusCategory, WithLookup } from '@hcengineering/core'
|
import core, { StatusCategory, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient, statusStore } from '@hcengineering/presentation'
|
||||||
import { IssueStatus } from '@hcengineering/tracker'
|
import { IssueStatus } from '@hcengineering/tracker'
|
||||||
import { getPlatformColor, IconSize } from '@hcengineering/ui'
|
import { IconSize, getPlatformColor } from '@hcengineering/ui'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { statusStore } from '@hcengineering/presentation'
|
|
||||||
import StatusIcon from '../icons/StatusIcon.svelte'
|
import StatusIcon from '../icons/StatusIcon.svelte'
|
||||||
|
|
||||||
export let value: WithLookup<IssueStatus>
|
export let value: WithLookup<IssueStatus>
|
||||||
@ -78,5 +77,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if icon !== undefined && color !== undefined && category !== undefined}
|
{#if icon !== undefined && color !== undefined && category !== undefined}
|
||||||
<StatusIcon {category} {size} fill={color} {statusIcon} />
|
<StatusIcon on:accent-color {category} {size} fill={color} {statusIcon} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
{#if value}
|
{#if value}
|
||||||
<div class="flex-presenter cursor-default" style:color={'inherit'}>
|
<div class="flex-presenter cursor-default" style:color={'inherit'}>
|
||||||
{#if !inline}
|
{#if !inline}
|
||||||
<IssueStatusIcon {value} {size} />
|
<IssueStatusIcon {value} {size} on:accent-color />
|
||||||
{/if}
|
{/if}
|
||||||
<span
|
<span
|
||||||
class="overflow-label"
|
class="overflow-label"
|
||||||
|
@ -31,5 +31,6 @@
|
|||||||
{kind}
|
{kind}
|
||||||
{colorInherit}
|
{colorInherit}
|
||||||
{accent}
|
{accent}
|
||||||
|
on:accent-color
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -13,55 +13,65 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { Doc, Ref, Space } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import ui, {
|
import ui, {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
AnyComponent,
|
AnyComponent,
|
||||||
|
Button,
|
||||||
|
IconAdd,
|
||||||
|
IconCollapseArrow,
|
||||||
IconMoreH,
|
IconMoreH,
|
||||||
Label,
|
Label,
|
||||||
hsvToRGB,
|
deviceOptionsStore as deviceInfo,
|
||||||
IconCollapseArrow,
|
eventToHTMLElement,
|
||||||
deviceOptionsStore as deviceInfo
|
hslToRgb,
|
||||||
|
rgbToHsl,
|
||||||
|
showPopup
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import { noCategory } from '../../viewOptions'
|
|
||||||
import view from '../../plugin'
|
|
||||||
import { Doc, Ref, Space } from '@hcengineering/core'
|
|
||||||
import { AttributeModel } from '@hcengineering/view'
|
import { AttributeModel } from '@hcengineering/view'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import view from '../../plugin'
|
||||||
|
import { noCategory } from '../../viewOptions'
|
||||||
|
|
||||||
export let groupByKey: string
|
export let groupByKey: string
|
||||||
export let category: any
|
export let category: any
|
||||||
export let headerComponent: AttributeModel | undefined
|
export let headerComponent: AttributeModel | undefined
|
||||||
export let space: Ref<Space> | undefined
|
export let space: Ref<Space> | undefined
|
||||||
export let createItemDialog: AnyComponent | undefined
|
|
||||||
export let createItemLabel: IntlString | undefined
|
|
||||||
export let limited: number
|
export let limited: number
|
||||||
export let items: Doc[]
|
export let items: Doc[]
|
||||||
export let extraHeaders: AnyComponent[] | undefined
|
|
||||||
export let flat = false
|
export let flat = false
|
||||||
export let collapsed = false
|
export let collapsed = false
|
||||||
export let lastCat = false
|
export let lastCat = false
|
||||||
export let props: Record<string, any> = {}
|
|
||||||
export let level: number
|
export let level: number
|
||||||
|
|
||||||
|
export let createItemDialog: AnyComponent | undefined
|
||||||
|
export let createItemLabel: IntlString | undefined
|
||||||
|
export let extraHeaders: AnyComponent[] | undefined
|
||||||
|
export let props: Record<string, any> = {}
|
||||||
export let newObjectProps: (doc: Doc) => Record<string, any> | undefined
|
export let newObjectProps: (doc: Doc) => Record<string, any> | undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
// const handleCreateItem = (event: MouseEvent) => {
|
|
||||||
// if (createItemDialog === undefined) return
|
|
||||||
// showPopup(createItemDialog, newObjectProps(items[0]), eventToHTMLElement(event))
|
|
||||||
// }
|
|
||||||
const hue = Math.random()
|
|
||||||
$: lth = $deviceInfo.theme === 'theme-light'
|
$: lth = $deviceInfo.theme === 'theme-light'
|
||||||
$: headerBGColor = lth ? hsvToRGB(hue, 0.15, 0.9) : hsvToRGB(hue, 0.15, 0.3)
|
|
||||||
$: headerTextColor = lth ? hsvToRGB(hue, 0.5, 0.5) : hsvToRGB(hue, 0.6, 0.95)
|
let accentColor = { h: 0, s: 0, l: 65 }
|
||||||
|
|
||||||
|
$: headerBGColor = !lth
|
||||||
|
? hslToRgb(accentColor.h, accentColor.s, accentColor.l / 1.5 + (mouseOver ? -20 : 0))
|
||||||
|
: hslToRgb(accentColor.h, accentColor.s, accentColor.l * 1.5 + (mouseOver ? -20 : 0))
|
||||||
|
|
||||||
|
const handleCreateItem = (event: MouseEvent) => {
|
||||||
|
if (createItemDialog === undefined) return
|
||||||
|
showPopup(createItemDialog, newObjectProps(items[0]), eventToHTMLElement(event))
|
||||||
|
}
|
||||||
|
let mouseOver = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if headerComponent || groupByKey === noCategory}
|
{#if headerComponent || groupByKey === noCategory}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
style:z-index={10 - level}
|
style:z-index={10 - level}
|
||||||
style:--list-header-color={headerBGColor.rgb}
|
|
||||||
style:--list-header-rgb-color={`${headerBGColor.r}, ${headerBGColor.g}, ${headerBGColor.b}`}
|
style:--list-header-rgb-color={`${headerBGColor.r}, ${headerBGColor.g}, ${headerBGColor.b}`}
|
||||||
class="flex-between categoryHeader row"
|
class="flex-between categoryHeader row"
|
||||||
class:gradient={!lth}
|
class:gradient={!lth}
|
||||||
@ -69,19 +79,30 @@
|
|||||||
class:collapsed
|
class:collapsed
|
||||||
class:subLevel={level !== 0}
|
class:subLevel={level !== 0}
|
||||||
class:lastCat
|
class:lastCat
|
||||||
|
on:focus={() => {
|
||||||
|
mouseOver = true
|
||||||
|
}}
|
||||||
|
on:mouseenter={() => {
|
||||||
|
mouseOver = true
|
||||||
|
}}
|
||||||
|
on:mouseover={() => {
|
||||||
|
mouseOver = true
|
||||||
|
}}
|
||||||
|
on:mouseleave={() => {
|
||||||
|
mouseOver = false
|
||||||
|
}}
|
||||||
on:click={() => dispatch('collapse')}
|
on:click={() => dispatch('collapse')}
|
||||||
>
|
>
|
||||||
<div class="flex-row-center clear-mins">
|
<div class="flex-row-center clear-mins">
|
||||||
{#if level === 0}
|
{#if level === 0}
|
||||||
<div class="chevron"><IconCollapseArrow size={'small'} /></div>
|
<div class="chevron"><IconCollapseArrow size={'small'} /></div>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- <FixedColumn key={`list_groupBy_${groupByKey}`} justify={'left'}> -->
|
|
||||||
{#if groupByKey === noCategory}
|
{#if groupByKey === noCategory}
|
||||||
<span style:color={headerTextColor.rgb} class="text-base fs-bold overflow-label pointer-events-none">
|
<span class="text-base fs-bold overflow-label pointer-events-none">
|
||||||
<Label label={view.string.NoGrouping} />
|
<Label label={view.string.NoGrouping} />
|
||||||
</span>
|
</span>
|
||||||
{:else if headerComponent}
|
{:else if headerComponent}
|
||||||
<span class="clear-mins" style:color={headerTextColor.rgb}>
|
<span class="clear-mins">
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={headerComponent.presenter}
|
this={headerComponent.presenter}
|
||||||
value={category}
|
value={category}
|
||||||
@ -90,17 +111,12 @@
|
|||||||
kind={'list-header'}
|
kind={'list-header'}
|
||||||
colorInherit={lth && level === 0}
|
colorInherit={lth && level === 0}
|
||||||
accent={level === 0}
|
accent={level === 0}
|
||||||
|
on:accent-color={(evt) => {
|
||||||
|
accentColor = rgbToHsl(evt.detail.r, evt.detail.g, evt.detail.b)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- </FixedColumn> -->
|
|
||||||
<!-- {#if extraHeaders}
|
|
||||||
{#each extraHeaders as extra}
|
|
||||||
<FixedColumn key={`list_groupBy_${groupByKey}_extra_${extra}`} justify={'left'}>
|
|
||||||
<Component is={extra} props={{ ...props, value: category, docs: items }} />
|
|
||||||
</FixedColumn>
|
|
||||||
{/each}
|
|
||||||
{/if} -->
|
|
||||||
{#if limited < items.length}
|
{#if limited < items.length}
|
||||||
<div class="antiSection-header__counter flex-row-center mx-2">
|
<div class="antiSection-header__counter flex-row-center mx-2">
|
||||||
<span class="caption-color">{limited}</span>
|
<span class="caption-color">{limited}</span>
|
||||||
@ -119,14 +135,16 @@
|
|||||||
<span class="antiSection-header__counter ml-2">{items.length}</span>
|
<span class="antiSection-header__counter ml-2">{items.length}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<!-- {#if createItemDialog !== undefined && createItemLabel !== undefined}
|
{#if createItemDialog !== undefined && createItemLabel !== undefined}
|
||||||
<Button
|
<div class:on-hover={!mouseOver}>
|
||||||
icon={IconAdd}
|
<Button
|
||||||
kind={'transparent'}
|
icon={IconAdd}
|
||||||
showTooltip={{ label: createItemLabel }}
|
kind={'transparent'}
|
||||||
on:click={handleCreateItem}
|
showTooltip={{ label: createItemLabel }}
|
||||||
/>
|
on:click={handleCreateItem}
|
||||||
{/if} -->
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -135,12 +153,16 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
padding: 0 2.5rem 0 0.75rem;
|
padding: 0 0.75rem 0 0.75rem;
|
||||||
height: 2.75rem;
|
height: 2.75rem;
|
||||||
min-height: 2.75rem;
|
min-height: 2.75rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
background: var(--theme-bg-color);
|
background: var(--theme-bg-color);
|
||||||
|
|
||||||
|
.on-hover {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.chevron {
|
.chevron {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@ -151,10 +173,14 @@
|
|||||||
transition: transform 0.15s ease-in-out;
|
transition: transform 0.15s ease-in-out;
|
||||||
}
|
}
|
||||||
&:not(.gradient)::before {
|
&:not(.gradient)::before {
|
||||||
background: var(--list-header-color);
|
background: rgba(var(--list-header-rgb-color), 0.15);
|
||||||
}
|
}
|
||||||
&.gradient::before {
|
&.gradient::before {
|
||||||
background: linear-gradient(90deg, var(--list-header-color), rgba(var(--list-header-rgb-color), 0.3));
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(var(--list-header-rgb-color), 0.15),
|
||||||
|
rgba(var(--list-header-rgb-color), 0.05)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
&::before {
|
&::before {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -187,7 +213,7 @@
|
|||||||
border-left: 1px solid var(--theme-list-border-color);
|
border-left: 1px solid var(--theme-list-border-color);
|
||||||
border-right: 1px solid var(--theme-list-border-color);
|
border-right: 1px solid var(--theme-list-border-color);
|
||||||
border-bottom: 1px solid var(--theme-list-subheader-divider);
|
border-bottom: 1px solid var(--theme-list-subheader-divider);
|
||||||
// here shoul be top 3rem for sticky, but with ExpandCollapse it gives strange behavior
|
// here should be top 3rem for sticky, but with ExpandCollapse it gives strange behavior
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: none;
|
content: none;
|
||||||
@ -206,8 +232,4 @@
|
|||||||
padding: 0 0.25rem 0 0.25rem;
|
padding: 0 0.25rem 0 0.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// .row:not(:last-child) {
|
|
||||||
// border-bottom: 1px solid var(--accent-bg-color);
|
|
||||||
// }
|
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user