mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-26 04:23:58 +03:00
TSK-1469,-1470: added SelectAvatars, UserBoxItems components (#3176)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
ee0a120450
commit
6423d1beeb
@ -290,7 +290,7 @@
|
||||
--theme-popup-hover: #F5F5F5;
|
||||
--theme-popup-divider: rgba(0, 0, 0, .1);
|
||||
--theme-popup-header: #EBEBEB;
|
||||
--theme-popup-shadow: 0px 1px 4px 2px rgba(0, 0, 0, 0.15);
|
||||
--theme-popup-shadow: 0px 0px 8px rgba(0, 0, 0, 0.2);
|
||||
--theme-panel-color: #FFFFFF;
|
||||
--theme-calendar-today-color: #000;
|
||||
--theme-calendar-holiday-color: #eb5757;
|
||||
|
@ -210,6 +210,8 @@ input.search {
|
||||
.flex-row-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
.flex-row-reverse {
|
||||
display: flex;
|
||||
@ -394,8 +396,8 @@ input.search {
|
||||
}
|
||||
}
|
||||
.gap-1-5 {
|
||||
& > * { margin-right: .375rem; }
|
||||
&.reverse > :last-child { margin-right: .375rem; }
|
||||
&:not(.reverse) > *:not(:last-child) { margin-right: .375rem; }
|
||||
&.reverse > *:not(:first-child) { margin-left: .375rem; }
|
||||
}
|
||||
.gap-2 {
|
||||
&:not(.reverse) > *:not(:first-child) { margin-left: .5rem; }
|
||||
@ -688,7 +690,7 @@ input.search {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
.svg-x-small, .svg-small, .svg-medium, .svg-large { flex-shrink: 0; }
|
||||
.svg-card, .svg-x-small, .svg-small, .svg-medium, .svg-large { flex-shrink: 0; }
|
||||
|
||||
.svg-mask {
|
||||
position: absolute;
|
||||
@ -716,6 +718,7 @@ a.no-line {
|
||||
.cursor-inherit { cursor: inherit; }
|
||||
|
||||
.pointer-events-none { pointer-events: none; }
|
||||
.content-pointer-events-none > * { pointer-events: none; }
|
||||
.select-text { user-select: text; }
|
||||
.select-text-i { user-select: text !important; }
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
export let items: Ref<Contact>[] = []
|
||||
export let size: IconSize
|
||||
export let limit: number = 3
|
||||
export let hideLimit: boolean = false
|
||||
|
||||
let persons: Contact[] = []
|
||||
const query = createQuery()
|
||||
@ -37,8 +38,13 @@
|
||||
</script>
|
||||
|
||||
<div class="avatars-container">
|
||||
{#each persons as person}
|
||||
<div class="combine-avatar {size}">
|
||||
{#each persons as person, i}
|
||||
<div
|
||||
class="combine-avatar {size}"
|
||||
data-over={i === persons.length - 1 && items.length > limit && !hideLimit
|
||||
? `+${items.length - limit + 1}`
|
||||
: undefined}
|
||||
>
|
||||
<Avatar avatar={person.avatar} {size} />
|
||||
</div>
|
||||
{/each}
|
||||
@ -52,9 +58,18 @@
|
||||
.combine-avatar.inline:not(:first-child) {
|
||||
margin-left: calc(1px - (0.875rem / 2));
|
||||
}
|
||||
.combine-avatar.tiny:not(:first-child) {
|
||||
margin-left: calc(1px - (1.13rem / 2));
|
||||
}
|
||||
.combine-avatar.card:not(:first-child) {
|
||||
margin-left: calc(1px - (1.25rem / 2));
|
||||
}
|
||||
.combine-avatar.x-small:not(:first-child) {
|
||||
margin-left: calc(1px - (1.5rem / 2));
|
||||
}
|
||||
.combine-avatar.smaller:not(:first-child) {
|
||||
margin-left: calc(1px - (1.75rem / 2));
|
||||
}
|
||||
.combine-avatar.small:not(:first-child) {
|
||||
margin-left: calc(1px - 1rem);
|
||||
}
|
||||
@ -70,5 +85,36 @@
|
||||
.combine-avatar:not(:last-child) {
|
||||
mask: radial-gradient(circle at 100% 50%, rgba(0, 0, 0, 0) 48.5%, rgb(0, 0, 0) 50%);
|
||||
}
|
||||
.combine-avatar.inline,
|
||||
.combine-avatar.tiny,
|
||||
.combine-avatar.card,
|
||||
.combine-avatar.x-small {
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
.combine-avatar[data-over^='+']:last-child {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: attr(data-over);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
color: var(--theme-caption-color);
|
||||
transform: translate(-53%, -52%);
|
||||
z-index: 2;
|
||||
}
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--theme-bg-color);
|
||||
border: 1px solid var(--theme-divider-color);
|
||||
border-radius: 50%;
|
||||
opacity: 0.9;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -91,7 +91,7 @@
|
||||
{#if contacts.length === 1}
|
||||
<ContactPresenter value={contacts[0]} disabled />
|
||||
{:else}
|
||||
<CombineAvatars {_class} bind:items size={'inline'} />
|
||||
<CombineAvatars {_class} bind:items size={'inline'} hideLimit />
|
||||
<span class="overflow-label ml-1-5">
|
||||
<Label label={contact.string.NumberMembers} params={{ count: contacts.length }} />
|
||||
</span>
|
||||
|
@ -0,0 +1,81 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import type { Class, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import type { IconSize } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { employeeByIdStore } from '../utils'
|
||||
import CombineAvatars from './CombineAvatars.svelte'
|
||||
import AddAvatar from './icons/AddAvatar.svelte'
|
||||
import UsersPopup from './UsersPopup.svelte'
|
||||
|
||||
export let items: Ref<Employee>[] = []
|
||||
export let _class: Ref<Class<Employee>> = contact.class.Employee
|
||||
export let docQuery: DocumentQuery<Employee> | undefined = {
|
||||
active: true
|
||||
}
|
||||
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let size: IconSize = 'small'
|
||||
export let width: string | undefined = undefined
|
||||
export let readonly: boolean = false
|
||||
export let limit: number = 6
|
||||
export let hideLimit: boolean = false
|
||||
|
||||
let persons: Employee[] = items.map((p) => $employeeByIdStore.get(p)).filter((p) => p !== undefined) as Employee[]
|
||||
$: persons = items.map((p) => $employeeByIdStore.get(p)).filter((p) => p !== undefined) as Employee[]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function addPerson (evt: Event): Promise<void> {
|
||||
showPopup(
|
||||
UsersPopup,
|
||||
{
|
||||
_class,
|
||||
label,
|
||||
docQuery,
|
||||
multiSelect: true,
|
||||
allowDeselect: false,
|
||||
selectedUsers: items,
|
||||
readonly
|
||||
},
|
||||
evt.target as HTMLElement,
|
||||
undefined,
|
||||
(result) => {
|
||||
if (result != null) {
|
||||
items = result
|
||||
dispatch('update', items)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="flex-row-center flex-nowrap content-pointer-events-none"
|
||||
class:cursor-pointer={!readonly}
|
||||
style:width={width ?? 'auto'}
|
||||
on:click={readonly ? () => {} : addPerson}
|
||||
>
|
||||
{#if persons.length > 0}
|
||||
<CombineAvatars {_class} bind:items {size} {limit} {hideLimit} />
|
||||
{:else}
|
||||
<AddAvatar {size} />
|
||||
{/if}
|
||||
</div>
|
124
plugins/contact-resources/src/components/UserBoxItems.svelte
Normal file
124
plugins/contact-resources/src/components/UserBoxItems.svelte
Normal file
@ -0,0 +1,124 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import type { Class, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { Label, showPopup, ActionIcon, IconClose, IconAdd, Icon } from '@hcengineering/ui'
|
||||
import type { IconSize } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../plugin'
|
||||
import { employeeByIdStore } from '../utils'
|
||||
import UserInfo from './UserInfo.svelte'
|
||||
import UsersPopup from './UsersPopup.svelte'
|
||||
|
||||
export let items: Ref<Employee>[] = []
|
||||
export let _class: Ref<Class<Employee>> = contact.class.Employee
|
||||
export let docQuery: DocumentQuery<Employee> | undefined = {
|
||||
active: true
|
||||
}
|
||||
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let actionLabel: IntlString = plugin.string.AddMember
|
||||
export let size: IconSize = 'x-small'
|
||||
export let width: string | undefined = undefined
|
||||
export let readonly: boolean = false
|
||||
|
||||
let persons: Employee[] = items.map((p) => $employeeByIdStore.get(p)).filter((p) => p !== undefined) as Employee[]
|
||||
$: persons = items.map((p) => $employeeByIdStore.get(p)).filter((p) => p !== undefined) as Employee[]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function addPerson (evt: Event): Promise<void> {
|
||||
showPopup(
|
||||
UsersPopup,
|
||||
{
|
||||
_class,
|
||||
label,
|
||||
docQuery,
|
||||
multiSelect: true,
|
||||
allowDeselect: false,
|
||||
selectedUsers: items,
|
||||
readonly
|
||||
},
|
||||
evt.target as HTMLElement,
|
||||
undefined,
|
||||
(result) => {
|
||||
if (result != null) {
|
||||
items = result
|
||||
dispatch('update', items)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const removePerson = (removed: Employee) => {
|
||||
const newItems = items.filter((it) => it !== removed._id)
|
||||
dispatch('update', newItems)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-col" style:width={width ?? 'auto'}>
|
||||
<div class="flex-row-center flex-wrap">
|
||||
{#each persons as person}
|
||||
<div class="usertag-container gap-1-5">
|
||||
<UserInfo value={person} {size} />
|
||||
<ActionIcon
|
||||
icon={IconClose}
|
||||
size={size === 'inline' ? 'x-small' : 'small'}
|
||||
action={() => removePerson(person)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if !readonly}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="addButton {size === 'inline' ? 'small' : 'medium'} overflow-label gap-2 cursor-pointer"
|
||||
class:mt-2={persons.length > 0}
|
||||
on:click={addPerson}
|
||||
>
|
||||
<span><Label label={actionLabel} /></span>
|
||||
<Icon icon={IconAdd} size={size === 'inline' ? 'x-small' : 'small'} fill={'var(--theme-dark-color)'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.usertag-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 0.5rem 0.5rem 0;
|
||||
padding: 0.375rem 0.625rem 0.375rem 0.5rem;
|
||||
background-color: var(--theme-button-enabled);
|
||||
border: 1px solid var(--theme-button-border);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.addButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
color: var(--theme-dark-color);
|
||||
|
||||
&.small {
|
||||
height: 0.875rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 0.75rem;
|
||||
}
|
||||
&.medium {
|
||||
height: 1.125rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -16,7 +16,8 @@
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import type { Class, DocumentQuery, Ref } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { Button, ButtonKind, ButtonSize, Label, showPopup, TooltipAlignment } from '@hcengineering/ui'
|
||||
import { Button, Label, showPopup } from '@hcengineering/ui'
|
||||
import type { ButtonKind, ButtonSize, TooltipAlignment } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../plugin'
|
||||
import { employeeByIdStore } from '../utils'
|
||||
@ -86,7 +87,7 @@
|
||||
{#if persons.length === 1}
|
||||
<UserInfo value={persons[0]} size={'inline'} />
|
||||
{:else}
|
||||
<CombineAvatars {_class} bind:items size={'inline'} />
|
||||
<CombineAvatars {_class} bind:items size={'inline'} hideLimit />
|
||||
<span class="overflow-label ml-1-5">
|
||||
<Label label={plugin.string.NumberMembers} params={{ count: persons.length }} />
|
||||
</span>
|
||||
|
@ -0,0 +1,82 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { IconSize } from '@hcengineering/ui'
|
||||
|
||||
export let size: IconSize
|
||||
export let fill: string = 'var(--theme-caption-color)'
|
||||
</script>
|
||||
|
||||
<svg class="svg-newavatar ava-{size}" viewBox="0 0 26 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle
|
||||
fill="transparent"
|
||||
stroke="var(--theme-trans-color)"
|
||||
stroke-width="1"
|
||||
stroke-dasharray="10%"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="12"
|
||||
/>
|
||||
<path
|
||||
fill="var(--theme-trans-color)"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M15.1,9.3c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1c0-1.7,1.4-3.1,3.1-3.1C13.7,6.2,15.1,7.5,15.1,9.3z M12,17.8c-2.5,0-4.7-0.4-4.7-2c0-1.6,2.2-2,4.7-2c2.5,0,4.7,0.4,4.7,2C16.7,17.4,14.5,17.8,12,17.8z"
|
||||
/>
|
||||
<circle fill="var(--theme-navpanel-color)" cx="20" cy="18" r="5.5" />
|
||||
<path
|
||||
fill="var(--theme-trans-color)"
|
||||
d="M20,13c2.8,0,5,2.2,5,5s-2.2,5-5,5s-5-2.2-5-5S17.2,13,20,13 M20,12c-3.3,0-6,2.7-6,6s2.7,6,6,6s6-2.7,6-6S23.3,12,20,12L20,12z"
|
||||
/>
|
||||
<path
|
||||
fill="var(--theme-trans-color)"
|
||||
d="M22.8,17.5h-2.2v-2.2c0-0.3-0.2-0.5-0.5-0.5s-0.5,0.2-0.5,0.5v2.2h-2.3c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h2.3v2.2c0,0.3,0.2,0.5,0.5,0.5s0.5-0.2,0.5-0.5v-2.2h2.2c0.3,0,0.5-0.2,0.5-0.5S23,17.5,22.8,17.5z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<style lang="scss">
|
||||
.svg-newavatar {
|
||||
overflow: visible;
|
||||
margin-right: -8%;
|
||||
}
|
||||
.ava-inline {
|
||||
height: 0.875rem;
|
||||
}
|
||||
.ava-tiny {
|
||||
height: 1.13rem;
|
||||
}
|
||||
.ava-card {
|
||||
height: 1.25rem;
|
||||
}
|
||||
.ava-x-small {
|
||||
height: 1.5rem;
|
||||
}
|
||||
.ava-smaller {
|
||||
height: 1.75rem;
|
||||
}
|
||||
.ava-small {
|
||||
height: 2rem;
|
||||
}
|
||||
.ava-medium {
|
||||
height: 2.25rem;
|
||||
}
|
||||
.ava-large {
|
||||
height: 4.5rem;
|
||||
}
|
||||
.ava-x-large {
|
||||
height: 7.5rem;
|
||||
}
|
||||
</style>
|
@ -73,6 +73,8 @@ 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 contact from './plugin'
|
||||
import {
|
||||
@ -122,7 +124,9 @@ export {
|
||||
SpaceMembers,
|
||||
CombineAvatars,
|
||||
UserInfo,
|
||||
IconMembers
|
||||
IconMembers,
|
||||
SelectAvatars,
|
||||
UserBoxItems
|
||||
}
|
||||
|
||||
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({
|
||||
@ -286,7 +290,9 @@ export default async (): Promise<Resources> => ({
|
||||
ChannelPresenter,
|
||||
ChannelPanel,
|
||||
ActivityChannelPresenter,
|
||||
SpaceMembers
|
||||
SpaceMembers,
|
||||
SelectAvatars,
|
||||
UserBoxItems
|
||||
},
|
||||
completion: {
|
||||
EmployeeQuery: async (
|
||||
|
Loading…
Reference in New Issue
Block a user