UBER-64,-231,-229: updated CreateProject and SelectAvatar layouts, fixed bugs (#3253)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2023-05-26 06:02:47 +03:00 committed by GitHub
parent 0298ff1c8d
commit ea6612cdb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 419 additions and 257 deletions

View File

@ -127,7 +127,7 @@ export class TTypeMilestoneStatus extends TType {}
@Model(tracker.class.Project, core.class.Space, DOMAIN_SPACE)
@UX(tracker.string.Project, tracker.icon.Issues, 'Project', 'name')
export class TProject extends TSpace implements Project {
@Prop(TypeString(), tracker.string.Identifier)
@Prop(TypeString(), tracker.string.ProjectIdentifier)
@Index(IndexKind.FullText)
identifier!: IntlString

View File

@ -30,7 +30,9 @@
export let fullSize: boolean = false
export let hideAttachments: boolean = false
export let hideSubheader: boolean = false
export let accentHeader: boolean = false
export let gap: string | undefined = undefined
export let width: 'large' | 'medium' | 'small' | 'x-small' = 'large'
const dispatch = createEventDispatcher()
@ -39,7 +41,7 @@
<form
id={label}
class="antiCard {$deviceInfo.isMobile ? 'mobile' : 'dialog'}"
class="antiCard {$deviceInfo.isMobile ? 'mobile' : 'dialog'} {width}"
class:full={fullSize}
on:submit|preventDefault={() => {}}
use:resizeObserver={() => {
@ -52,7 +54,7 @@
<slot name="header" />
<span class="antiCard-header__divider"><IconForward size={'small'} /></span>
{/if}
<span class="antiCard-header__title">
<span class="antiCard-header__title" class:accentHeader>
{#if $$slots.title}
<slot name="title" {label} labelProps={labelProps ?? {}} />
{:else}

View File

@ -36,6 +36,7 @@
export let enableFormatting = false
export let autofocus = false
export let enableBackReferences: boolean = false
export let isScrollable: boolean = true
const Mode = {
View: 1,
@ -159,6 +160,7 @@
{focusable}
{enableFormatting}
{autofocus}
{isScrollable}
extensions={enableBackReferences ? [completionPlugin] : []}
bind:content={rawValue}
bind:this={textEditor}

View File

@ -646,13 +646,19 @@
// max-height: 100%;
height: 100%;
}
&:not(.showScroll)::-webkit-scrollbar-thumb {
background-color: transparent;
}
&.scrollable {
overflow: auto;
max-height: var(--texteditor-maxheight);
&.showScroll {
overflow: auto;
}
}
&:not(.showScroll) {
overflow-y: hidden;
&::-webkit-scrollbar-thumb {
background-color: transparent;
}
}
}

View File

@ -59,7 +59,7 @@
--primary-button-hovered: #1A53AF;
--primary-button-pressed: #144CA8;
--primary-button-focused: #144CA8;
--primary-button-disabled: #6E9FED;
--primary-button-disabled: #484662;
--primary-button-focused-border: #6E9FED;
--primary-button-border: rgba(255, 255, 255, .09);
--primary-button-outline: rgba(87, 132, 255, .3); // OLD
@ -142,6 +142,13 @@
--theme-calendar-holiday-bgcolor: rgba(235, 87, 87, .1);
--theme-calendar-weekend-bgcolor: rgba(242, 153, 74, .05);
--theme-toggle-sw-color: #fff;
--theme-toggle-on-sw-color: #fff;
--theme-toggle-bg-color: rgba(120, 120, 128, 0.32);
--theme-toggle-bg-hover: rgba(120, 120, 128, 0.64);
--theme-toggle-on-bg-color: #205dc2;
--theme-toggle-on-bg-hover: #1A53AF;
--theme-error-color: #eb5757;
--theme-urgent-color: #F5694A;
--theme-warning-color: #f2994a;
@ -208,13 +215,6 @@
--dangerous-bg-hover: #ff6464;
--dangerous-shadow: var(--dangerous-bg-color) 0px 0px 12px -1px;
--toggle-sw-color: #27282b;
--toggle-on-sw-color: #fff;
--toggle-bg-color: #3c3f44;
--toggle-bg-hover: #45484e;
--toggle-on-bg-color: #5e6ad2;
--toggle-on-bg-hover: #828fff;
--incoming-msg: rgba(67, 67, 72, .3);
--outcoming-msg: rgba(67, 67, 72, .6);
@ -226,12 +226,12 @@
/* Light Theme */
.theme-light {
--primary-button-color: #fff;
--primary-button-enabled: rgb(43, 82, 144);
--primary-button-transparent: rgba(43, 82, 144, 0.2);
--primary-button-hovered: #1A53AF; // DARK
--primary-button-pressed: #144CA8; // DARK
--primary-button-focused: #144CA8; // DARK
--primary-button-disabled: #6E9FED; // DARK
--primary-button-enabled: #205DC2;
--primary-button-transparent: rgba(32, 93, 194, 0.2);
--primary-button-hovered: #1A53AF;
--primary-button-pressed: #144CA8;
--primary-button-focused: #144CA8;
--primary-button-disabled: #EBEBEB;
--primary-button-focused-border: #6E9FED; // DARK
--primary-button-border: rgba(255, 255, 255, .09);
--primary-button-outline: rgba(87, 132, 255, .3); // OLD
@ -313,6 +313,12 @@
--theme-calendar-today-bgcolor: rgba(43, 81, 144, .1);
--theme-calendar-holiday-bgcolor: rgba(235, 87, 87, .1);
--theme-calendar-weekend-bgcolor: rgba(242, 153, 74, .1);
--theme-toggle-sw-color: #fff;
--theme-toggle-on-sw-color: #fff;
--theme-toggle-bg-color: rgba(120, 120, 128, 0.32);
--theme-toggle-bg-hover: rgba(120, 120, 128, 0.64);
--theme-toggle-on-bg-color: #205dc2;
--theme-toggle-on-bg-hover: #1A53AF;
--theme-error-color: #eb5757; // Dark
--theme-urgent-color: #F5694A;
@ -380,13 +386,6 @@
--dangerous-bg-hover: #d44e4e;
--dangerous-shadow: var(--dangerous-bg-color) 0px 0px 12px -1px;
--toggle-sw-color: #fff;
--toggle-on-sw-color: #fff;
--toggle-bg-color: #dfe1e4;
--toggle-bg-hover: #c9cbcd;
--toggle-on-bg-color: #6e79d6;
--toggle-on-bg-hover: #535db3;
--incoming-msg: rgba(67, 67, 72, .1);
--outcoming-msg: rgba(67, 67, 72, .2);

View File

@ -413,6 +413,8 @@ input.search {
&.reverse > *:not(:last-child) { margin-right: 1rem; }
}
.gapV-4 > *:not(:last-child) { margin-bottom: 1rem; }
.gapV-6 > *:not(:last-child) { margin-bottom: 1.5rem; }
.gapV-8 > *:not(:last-child) { margin-bottom: 2rem; }
.gap-around-2 > * { margin: .25rem; }
.gap-around-4 > * { margin: .5rem; }

View File

@ -256,6 +256,49 @@
padding: .5rem 0 1.25rem;
}
/* Basic */
.antiGrid {
display: flex;
flex-direction: column;
flex-shrink: 0;
min-width: 0;
min-height: 0;
&-row {
display: flex;
align-items: center;
min-width: 0;
&__header {
width: 15rem;
padding-right: 1rem;
color: var(--theme-caption-color);
&.withDesciption {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
span {
font-size: .75rem;
color: var(--theme-halfcontent-color);
}
}
&.topAlign {
align-self: flex-start;
margin-top: .75rem;
}
}
.padding {
flex-grow: 1;
padding: .75rem 0;
}
&:not(:last-child) { margin-bottom: .5rem; }
& > *:not(.padding, .topAlign) { margin: .25rem 0; }
}
}
/* Basic */
.antiTitle {
.icon-wrapper, &.icon-wrapper,

View File

@ -58,6 +58,8 @@
min-width: 0;
line-height: 150%;
color: var(--theme-caption-color);
&.accentHeader { font-size: 1rem; }
}
&__divider { color: var(--theme-dark-color); }
&__error {
@ -190,10 +192,25 @@
}
&.dialog {
width: 45rem;
height: auto;
max-width: 60rem;
max-height: inherit;
&.large {
width: 45rem;
max-width: 60rem;
}
&.medium {
width: 37.5rem;
max-width: 37.5rem;
}
&.small {
width: 30rem;
max-width: 30rem;
}
&.x-small {
width: 25rem;
max-width: 25rem;
}
&.full {
width: max-content;
// max-width: 100%;

View File

@ -37,8 +37,9 @@
export let select: boolean = false
export let focusable: boolean = false
export let disabled: boolean = false
export let fullSize = false
export let required = false
export let fullSize: boolean = false
export let required: boolean = false
export let uppercase: boolean = false
const dispatch = createEventDispatcher()
@ -134,6 +135,7 @@
class="editbox-container"
class:flex-grow={fullSize}
class:w-full={focusable || fullSize}
class:uppercase
on:click={() => {
input.focus()
}}
@ -210,8 +212,8 @@
align-items: flex-start;
.large-style {
font-weight: 500;
font-size: 1.125rem;
font-weight: 400;
font-size: 1.25rem;
}
.small-style {
font-weight: 400;
@ -271,5 +273,9 @@
input[type='number'] {
-moz-appearance: textfield;
}
&.uppercase .hidden-text,
&.uppercase input {
text-transform: uppercase;
}
}
</style>

View File

@ -22,7 +22,7 @@
export let selected: string | string[] = ''
export let multiselect: boolean = false
export let items: TabItem[]
export let kind: 'normal' | 'secondary' | 'plain' | 'separated' = 'normal'
export let kind: 'normal' | 'secondary' | 'plain' | 'separated' | 'separated-free' = 'normal'
export let onlyIcons: boolean = false
export let size: 'small' | 'medium' = 'medium'
@ -50,7 +50,8 @@
<div
bind:this={tabs[i]}
class={kind === 'normal' || kind === 'secondary' ? 'button' : 'plain'}
class:separated={kind === 'separated'}
class:separated={kind === 'separated' || kind === 'separated-free'}
class:free={kind === 'separated-free'}
class:onlyIcons
class:selected={getSelected(item.id, selected)}
data-view={item.tooltip}
@ -234,6 +235,9 @@
background-color: var(--theme-tablist-plain-divider);
transform: translateY(-50%);
}
&.free:first-child::before {
content: none;
}
}
}
}

View File

@ -37,9 +37,9 @@
<style lang="scss">
.toggle {
display: inline-block;
width: 2rem;
min-width: 2rem;
height: 1.5rem;
width: 2.25rem;
min-width: 2.25rem;
height: 1.25rem;
// line-height: 1.75rem;
vertical-align: middle;
font-size: inherit;
@ -55,13 +55,13 @@
overflow: hidden;
&:checked + .toggle-switch {
background-color: var(--toggle-on-bg-color);
background-color: var(--theme-toggle-on-bg-color);
&:hover {
background-color: var(--toggle-on-bg-hover);
background-color: var(--theme-toggle-on-bg-hover);
}
&:before {
left: 0.75rem;
background: var(--toggle-on-sw-color);
left: 1.125rem;
background: var(--theme-toggle-on-sw-color);
}
}
&:not(:disabled) + .toggle-switch {
@ -79,26 +79,26 @@
.toggle-switch {
position: relative;
display: inline-block;
width: 2rem;
height: 1.5rem;
border-radius: 4.5rem;
background-color: var(--toggle-bg-color);
width: 2.25rem;
height: 1.25rem;
border-radius: 1.25rem;
background-color: var(--theme-toggle-bg-color);
transition: left 0.2s, background-color 0.2s;
&:before {
content: '';
position: absolute;
top: 0.25rem;
left: 0.25rem;
top: 0.125rem;
left: 0.125rem;
display: inline-block;
width: 1rem;
height: 1rem;
border-radius: 50%;
background: var(--toggle-sw-color);
box-shadow: 1px 2px 7px rgba(119, 129, 142, 0.1);
background: var(--theme-toggle-sw-color);
box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.15), 0px 3px 1px rgba(0, 0, 0, 0.06);
transition: 0.15s;
}
&:hover {
background-color: var(--toggle-bg-hover);
background-color: var(--theme-toggle-bg-hover);
}
}
}

View File

@ -71,6 +71,7 @@
class:dateTimeButtonNoLabel={!shouldShowLabel}
class:text-xs={size === 'x-small'}
class:noDate={!value}
class:withIcon={showIcon}
style:width
on:click={(e) => {
if (editable && !opened) {
@ -292,12 +293,17 @@
}
}
&.secondary {
padding: 0 0.625rem;
color: var(--theme-caption-color);
background-color: var(--theme-button-enabled);
border-color: var(--theme-button-border);
border-radius: 0.25rem;
&.withIcon {
padding: 0 1rem 0 0.75rem;
}
&:not(.withIcon) {
padding: 0 0.75rem;
}
.btn-icon {
color: var(--theme-content-color);
}

View File

@ -185,6 +185,7 @@ export type IconSize =
| 'medium'
| 'large'
| 'x-large'
| '2x-large'
| 'full'
export interface DateOrShift {

View File

@ -94,25 +94,28 @@
{trimFilename(value.name)}
</a>
</div>
<div class="info-content">
{filesize(value.size, { spacer: '' })}
<a class="no-line colorInherit" href={getFileUrl(value.file)} download={value.name} bind:this={download}>
<Label label={presentation.string.Download} />
</a>
{#if removable}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="remove-link"
on:click={(ev) => {
ev.stopPropagation()
ev.preventDefault()
dispatch('remove', value)
}}
>
<Label label={presentation.string.Delete} />
</span>
{/if}
<div class="info-content flex-row-center">
{filesize(value.size, { spacer: '' })}
<span class="actions inline-flex clear-mins ml-1 gap-1">
<span></span>
<a class="no-line colorInherit" href={getFileUrl(value.file)} download={value.name} bind:this={download}>
<Label label={presentation.string.Download} />
</a>
{#if removable}
<span></span>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="remove-link"
on:click={(ev) => {
ev.stopPropagation()
ev.preventDefault()
dispatch('remove', value)
}}
>
<Label label={presentation.string.Delete} />
</span>
{/if}
</span>
</div>
</div>
</div>
@ -124,7 +127,6 @@
height: 3rem;
min-width: 14rem;
max-width: 19rem;
background-color: var(--theme-button-hovered);
border-radius: 0.25rem;
.icon {
@ -152,20 +154,41 @@
padding: 0.5rem 0.75rem;
width: 100%;
height: 100%;
background-color: var(--theme-button-enabled);
border: 1px solid var(--theme-button-border);
border-left: none;
border-radius: 0 0.25rem 0.25rem 0;
}
.no-line:hover ~ .info-container,
.info-container:hover {
background-color: var(--theme-button-hovered);
.actions {
opacity: 1;
}
}
.name {
white-space: nowrap;
font-size: 0.8125rem;
color: var(--theme-caption-color);
cursor: pointer;
&:hover ~ .info-content .actions {
opacity: 1;
}
}
.info-content {
white-space: nowrap;
font-size: 0.6875rem;
color: var(--theme-darker-color);
.actions {
opacity: 0;
transition: opacity 0.1s var(--timing-main);
}
&:hover .actions {
opacity: 1;
}
}
.remove-link {
color: var(--theme-error-color);

View File

@ -339,7 +339,7 @@
on:dragleave={() => {}}
on:drop|preventDefault|stopPropagation={(ev) => {
if (fakeAttach === 'fake') dispatch('attach', { action: 'drop', event: ev })
else if (fakeAttach === 'normal') fileDrop(ev)
else fileDrop(ev)
}}
>
<div class="expand-collapse">

View File

@ -84,7 +84,8 @@
"DisplayName": "Display name",
"SelectAvatar": "Select avatar",
"AvatarProvider": "Avatar provider",
"GravatarsManaged": "Gravatars are managed through",
"GravatarsManaged": "Gravatars are managed",
"Through": "through",
"CategoryProjectMembers": "Project members",
"AddMembersHeader": "Add members to {value}:",
"Assigned": "Assigned",

View File

@ -84,7 +84,8 @@
"MergeEmployeeTo": "В сотрудника",
"DisplayName": "Отображаемое имя",
"SelectAvatar": "Выбрать аватар",
"GravatarsManaged": "Граватары управляются через",
"GravatarsManaged": "Граватары управляются",
"Through": "через",
"AddMembersHeader": "Добавить пользователей в {value}:",
"NumberMembers": "{count, plural, =0 {нет участников} =1 {1 участник} =2 {2 участника} =3 {3 участника} =4 {4 участника} other {# участников}}",
"Assigned": "Назначен",

View File

@ -156,7 +156,7 @@
<div class="flex-presenter not-selected">
{#if icon}
<div class="icon w-4 flex-no-shrink" class:small-gap={size === 'inline' || size === 'small'}>
<Icon {icon} size={avatarSize} />
<Icon {icon} size={'small'} />
</div>
{/if}
<div class="label no-underline">

View File

@ -73,7 +73,7 @@
<div class="ava-{size} flex-center avatar-container" class:no-img={!url}>
{#if url}
{#if size === 'large' || size === 'x-large'}
{#if size === 'large' || size === 'x-large' || size === '2x-large'}
<img class="ava-{size} ava-blur" src={url} alt={''} bind:this={imageElement} />
{/if}
<img class="ava-{size} ava-mask" src={url} alt={''} bind:this={imageElement} />
@ -138,6 +138,10 @@
width: 7.5rem; // 120
height: 7.5rem;
}
.ava-2x-large {
width: 10rem; // 120
height: 10rem;
}
.ava-blur {
position: absolute;
@ -148,6 +152,11 @@
border: 1px solid var(--avatar-border-color);
border-radius: 50%;
}
.ava-large .ava-mask,
.ava-x-large .ava-mask,
.ava-2x-large .ava-mask {
border-width: 2px;
}
.ava-inline .ava-mask,
.ava-inline.no-img,

View File

@ -60,21 +60,17 @@
dispatch('close')
}}
/>
<div class="root">
<div class="editavatar-container">
{#await CropperP then Cropper}
<div class="cropper">
<Cropper bind:this={cropper} image={file} />
</div>
<div class="footer">
<div>
<Button label={presentation.string.Save} kind={'primary'} on:click={onCrop} />
</div>
<div class="ml-4 mr-4">
<Button label={presentation.string.Change} on:click={selectAnother} />
</div>
<div>
<Button label={presentation.string.Remove} on:click={remove} />
<Button label={presentation.string.Save} kind={'primary'} size={'large'} on:click={onCrop} />
<div class="mx-3 clear-mins">
<Button label={presentation.string.Change} size={'large'} on:click={selectAnother} />
</div>
<Button label={presentation.string.Remove} size={'large'} on:click={remove} />
</div>
{/await}
</div>
@ -87,11 +83,11 @@
bottom: 0;
right: 0;
background: var(--card-overlay-color);
background: var(--theme-overlay-color);
touch-action: none;
}
.root {
.editavatar-container {
position: absolute;
top: 0;
bottom: 0;
@ -105,21 +101,20 @@
transform: translate(-50%, -50%);
background: var(--popup-bg-color);
background: var(--theme-popup-color);
border-radius: 1.25rem;
box-shadow: 0px 44px 154px rgba(0, 0, 0, 0.75);
box-shadow: var(--theme-popup-shadow);
display: grid;
grid-template-rows: minmax(min-content, 1fr) auto;
}
.cropper {
width: inherit;
}
.footer {
display: flex;
flex-direction: row-reverse;
padding: 1rem 1.5rem;
.cropper {
width: inherit;
}
.footer {
display: flex;
flex-direction: row-reverse;
padding: 1rem 1.5rem;
}
}
</style>

View File

@ -17,7 +17,7 @@
import { AvatarType, buildGravatarId, checkHasGravatar, getAvatarColorForId } from '@hcengineering/contact'
import { Asset } from '@hcengineering/platform'
import { AnySvelteComponent, DropdownLabelsIntl, Label, showPopup } from '@hcengineering/ui'
import { AnySvelteComponent, Label, showPopup, TabList } from '@hcengineering/ui'
import presentation, { Card, getFileUrl } from '@hcengineering/presentation'
import contact from '../plugin'
@ -29,7 +29,7 @@
export let email: string | undefined
export let id: string
export let file: Blob | undefined
export let icon: Asset | AnySvelteComponent | undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let onSubmit: (avatarType?: AvatarType, avatar?: string, file?: Blob) => void
const [schema, uri] = avatar?.split('://') || []
@ -151,6 +151,8 @@
<Card
label={contact.string.SelectAvatar}
okLabel={presentation.string.Save}
width={'x-small'}
accentHeader
canSave={selectedAvatarType !== initialSelectedType ||
selectedAvatar !== initialSelectedAvatar ||
selectedFile !== file ||
@ -161,36 +163,39 @@
}}
on:changeContent
>
<div class="flex-row-center">
<Label label={contact.string.AvatarProvider} />
<DropdownLabelsIntl
kind={'link-bordered'}
<div class="flex-col-center gapV-4 mx-6">
{#if selectedAvatarType === AvatarType.IMAGE}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="cursor-pointer" on:click|self={handleImageAvatarClick}>
<AvatarComponent avatar={selectedAvatar} direct={selectedFile} size={'2x-large'} {icon} />
</div>
{:else}
<AvatarComponent avatar={`${selectedAvatarType}://${selectedAvatar}`} size={'2x-large'} {icon} />
{/if}
<TabList
items={getAvatarTypeDropdownItems(hasGravatar)}
label={contact.string.SelectAvatar}
kind={'separated-free'}
bind:selected={selectedAvatarType}
on:selected={handleDropdownSelection}
on:select={handleDropdownSelection}
/>
</div>
{#if selectedAvatarType === AvatarType.IMAGE}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="cursor-pointer" on:click|self={handleImageAvatarClick}>
<AvatarComponent avatar={selectedAvatar} direct={selectedFile} size={'x-large'} {icon} />
</div>
{:else}
<AvatarComponent avatar={`${selectedAvatarType}://${selectedAvatar}`} size={'x-large'} {icon} />
{/if}
{#if selectedAvatarType === AvatarType.GRAVATAR}
<span>
<Label label={contact.string.GravatarsManaged} />
<a target="”_blank”" href="//gravatar.com">Gravatar.com</a>
</span>
{/if}
<input
style="display: none;"
type="file"
bind:this={inputRef}
on:change={onSelectFile}
on:click={() => (document.body.onfocus = handleFileSelectionCancel)}
accept={targetMimes.join(',')}
/>
<svelte:fragment slot="footer">
{#if selectedAvatarType === AvatarType.GRAVATAR}
<div class="flex-col">
<Label label={contact.string.GravatarsManaged} />
<span class="inline-flex clear-mins">
<Label label={contact.string.Through} />
<a target="”_blank”" class="ml-1" href="//gravatar.com">Gravatar.com</a>
</span>
</div>
{/if}
<input
style="display: none;"
type="file"
bind:this={inputRef}
on:change={onSelectFile}
on:click={() => (document.body.onfocus = handleFileSelectionCancel)}
accept={targetMimes.join(',')}
/>
</svelte:fragment>
</Card>

View File

@ -85,9 +85,9 @@
{#if persons.length > 0}
<div class="flex-row-center flex-nowrap pointer-events-none">
{#if persons.length === 1}
<UserInfo value={persons[0]} size={'inline'} />
<UserInfo value={persons[0]} size={'card'} />
{:else}
<CombineAvatars {_class} bind:items size={'inline'} hideLimit />
<CombineAvatars {_class} bind:items size={'card'} hideLimit />
<span class="overflow-label ml-1-5">
<Label label={plugin.string.NumberMembers} params={{ count: persons.length }} />
</span>

View File

@ -21,7 +21,7 @@
export let fill: string = 'var(--caption-color)'
</script>
<svg class="svg-avatar {size}" {fill} viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<svg class="svg-avatar avaicon-{size}" {fill} viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<circle class="op" cx="20" cy="13.6" r="6.4" />
<path
d="M33.1,33.3c-0.8-2.2-2.5-4.2-4.9-5.5c-2.3-1.3-5.2-2.1-8.2-2.1s-5.8,0.7-8.2,2.1c-2.4,1.4-4.1,3.3-4.9,5.5 c-0.1,0.4,0.1,0.8,0.5,1c0.4,0.1,0.8-0.1,1-0.5c0.7-1.9,2.2-3.5,4.2-4.7c2.1-1.2,4.7-1.9,7.4-1.9c2.7,0,5.3,0.7,7.4,1.9 c2.1,1.2,3.6,2.9,4.2,4.7c0.1,0.3,0.4,0.5,0.7,0.5c0.1,0,0.2,0,0.3,0C33,34.1,33.2,33.7,33.1,33.3z"
@ -37,38 +37,42 @@
opacity: 0.05;
}
}
.inline {
.avaicon-inline {
width: 0.75rem;
height: 0.75rem;
}
.tiny {
.avaicon-tiny {
width: 0.875rem;
height: 0.875rem;
}
.x-small {
.avaicon-x-small {
width: 1rem;
height: 1rem;
}
.smaller {
.avaicon-smaller {
width: 1.125rem;
height: 1.125rem;
}
.small {
.avaicon-small {
width: 1.25rem;
height: 1.25rem;
}
.medium {
.avaicon-medium {
width: 1.5rem;
height: 1.5rem;
}
.large {
.avaicon-large {
width: 1.75rem;
height: 1.75rem;
}
.x-large {
.avaicon-x-large {
width: 3rem;
height: 3rem;
}
.avaicon-2x-large {
width: 4rem;
height: 4rem;
}
</style>

View File

@ -66,6 +66,7 @@ export default mergeIds(contactId, contact, {
MergeEmployeeTo: '' as IntlString,
SelectAvatar: '' as IntlString,
GravatarsManaged: '' as IntlString,
Through: '' as IntlString,
AvatarProvider: '' as IntlString,
CategoryProjectMembers: '' as IntlString,

View File

@ -30,13 +30,7 @@ import {
import { Client, Doc, getCurrentAccount, IdMap, ObjQueryType, Ref, Timestamp, toIdMap } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { TemplateDataProvider } from '@hcengineering/templates'
import {
DropdownIntlItem,
getCurrentResolvedLocation,
getPanelURI,
Location,
ResolvedLocation
} from '@hcengineering/ui'
import { TabItem, getCurrentResolvedLocation, getPanelURI, Location, ResolvedLocation } from '@hcengineering/ui'
import view, { Filter } from '@hcengineering/view'
import { FilterQuery } from '@hcengineering/view-resources'
import { get, writable } from 'svelte/store'
@ -269,21 +263,21 @@ function fillStores (): void {
fillStores()
export function getAvatarTypeDropdownItems (hasGravatar: boolean): DropdownIntlItem[] {
export function getAvatarTypeDropdownItems (hasGravatar: boolean): TabItem[] {
return [
{
id: AvatarType.COLOR,
label: contact.string.UseColor
labelIntl: contact.string.UseColor
},
{
id: AvatarType.IMAGE,
label: contact.string.UseImage
labelIntl: contact.string.UseImage
},
...(hasGravatar
? [
{
id: AvatarType.GRAVATAR,
label: contact.string.UseGravatar
labelIntl: contact.string.UseGravatar
}
]
: [])

View File

@ -40,10 +40,13 @@
"Canceled": "Canceled",
"CreateProject": "Create project",
"NewProject": "New project",
"ProjectTitlePlaceholder": "Project title",
"Identifier": "Project Identifier",
"ProjectTitle": "Project title",
"ProjectTitlePlaceholder": "New project",
"UsedInIssueIDs": "Used in issue IDs",
"Identifier": "Identifier",
"ProjectIdentifier": "Project Identifier",
"IdentifierExists": "Project identifier already exists",
"ProjectIdentifierPlaceholder": "Project Identifier",
"ProjectIdentifierPlaceholder": "PRJCT",
"ChooseIcon": "Choose icon",
"AddIssue": "Add Issue",
"NewIssue": "New issue",
@ -78,7 +81,7 @@
"AssignTo": "Assign to...",
"AssignedTo": "Assigned to {value}",
"Parent": "Parent issue",
"SetParent": "Set parent issue",
"SetParent": "Set parent issue\u2026",
"ChangeParent": "Change parent issue\u2026",
"RemoveParent": "Remove parent issue",
"OpenParent": "Open parent issue",
@ -93,7 +96,6 @@
"Labels": "Labels",
"Component": "Component",
"Space": "",
"NoDueDate": "No due date",
"SetDueDate": "Set due date\u2026",
"ChangeDueDate": "Change due date\u2026",
"ModificationDate": "Updated {value}",
@ -106,7 +108,7 @@
"TypeIssuePriority": "Issue priority",
"IssueTitlePlaceholder": "Issue title",
"SubIssueTitlePlaceholder": "Sub-issue title",
"IssueDescriptionPlaceholder": "Add description",
"IssueDescriptionPlaceholder": "Add description\u2026",
"SubIssueDescriptionPlaceholder": "Add sub-issue description",
"AddIssueTooltip": "Add issue...",
"NewIssueDialogClose": "Do you want to close this dialog?",
@ -264,7 +266,7 @@
"CurrentWorkDay": "Current Working Day",
"PreviousWorkDay": "Previous Working Day",
"TimeReportDayTypeLabel": "Select time report day type",
"DefaultAssignee": "Select default assignee for issues",
"DefaultAssignee": "Default assignee for issues",
"SevenHoursLength": "Seven Hours",
"EightHoursLength": "Eight Hours",

View File

@ -40,10 +40,13 @@
"Canceled": "Отменено",
"CreateProject": "Создать проект",
"NewProject": "Новый проект",
"ProjectTitlePlaceholder": "Название проекта",
"Identifier": "Идентификатор проекта",
"ProjectTitle": "Название проекта",
"ProjectTitlePlaceholder": "Новый проект",
"UsedInIssueIDs": "Используется в идентификаторах задач",
"Identifier": "Идентификатор",
"ProjectIdentifier": "Идентификатор проекта",
"IdentifierExists": "Идентификатор уже существует проекта",
"ProjectIdentifierPlaceholder": "Идентификатор проекта",
"ProjectIdentifierPlaceholder": "ПКТ",
"ChooseIcon": "Выбрать иконку",
"AddIssue": "Добавить задачу",
"NewIssue": "Новая задача",
@ -78,7 +81,7 @@
"AssignTo": "Назначить...",
"AssignedTo": "Назначен на {value}",
"Parent": "Родительская задача",
"SetParent": "Задать родительскую задачу",
"SetParent": "Задать родительскую задачу\u2026",
"ChangeParent": "Изменить родительскую задачу\u2026",
"RemoveParent": "Удалить родительскую задачу",
"OpenParent": "Открыть родительскую задачу",
@ -93,7 +96,6 @@
"Labels": "Метки",
"Component": "Компонент",
"Space": "",
"NoDueDate": "Нет срока выполнения",
"SetDueDate": "Указать срок выполнения\u2026",
"ChangeDueDate": "Изменить срок выполнения\u2026",
"ModificationDate": "Изменено {value}",
@ -106,7 +108,7 @@
"TypeIssuePriority": "Приоритет задачи",
"IssueTitlePlaceholder": "Имя задачи",
"SubIssueTitlePlaceholder": "Имя подзадачи",
"IssueDescriptionPlaceholder": "Описание задачи",
"IssueDescriptionPlaceholder": "Добавить описание\u2026",
"SubIssueDescriptionPlaceholder": "Описание подзадачи",
"AddIssueTooltip": "Добавить задачу\u2026",
"NewIssueDialogClose": "Вы действительно хотите закрыть окно?",
@ -264,7 +266,7 @@
"CurrentWorkDay": "Текущий Рабочий День",
"PreviousWorkDay": "Предыдущий Рабочий День",
"TimeReportDayTypeLabel": "Выберите тип дня для временного отчета",
"DefaultAssignee": "Выберите исполнителя по умолчанию",
"DefaultAssignee": "Исполнитель задачи по умолчанию",
"SevenHoursLength": "Семь Часов",
"EightHoursLength": "Восемь Часов",

View File

@ -587,7 +587,6 @@
alwaysEdit
showButtons={false}
kind={'indented'}
fakeAttach={'hidden'}
enableBackReferences={true}
bind:content={object.description}
placeholder={tracker.string.IssueDescriptionPlaceholder}
@ -708,8 +707,7 @@
<div id="duedate-editor" class="new-line">
<DatePresenter
bind:value={object.dueDate}
labelNull={tracker.string.NoDueDate}
icon={tracker.icon.DueDate}
labelNull={tracker.string.DueDate}
kind={'secondary'}
size={'large'}
editable

View File

@ -59,7 +59,7 @@
$: handleSelectedMilestoneIdUpdated(value, rawMilestones)
$: translate(tracker.string.NoMilestone, {}).then((result) => (defaultMilestoneLabel = result))
$: translate(tracker.string.Milestone, {}).then((result) => (defaultMilestoneLabel = result))
const milestoneIcon = tracker.icon.Milestone
$: milestoneText = shouldShowLabel ? selectedMilestone?.label ?? defaultMilestoneLabel : undefined
@ -81,7 +81,7 @@
{
id: null,
icon: tracker.icon.Milestone,
label: tracker.string.NoMilestone,
label: tracker.string.Milestone,
isSelected: sp === undefined
},
...rawMilestones.map((p) => ({

View File

@ -25,7 +25,7 @@
</script>
<Card
label={projects.has(identifier) ? tracker.string.IdentifierExists : tracker.string.Identifier}
label={projects.has(identifier) ? tracker.string.IdentifierExists : tracker.string.ProjectIdentifier}
okLabel={presentation.string.Save}
okAction={save}
canSave={identifier !== project.identifier && !projects.has(identifier)}

View File

@ -26,7 +26,7 @@
IconEdit,
IconWithEmojii,
Label,
ToggleWithLabel,
Toggle,
eventToHTMLElement,
getColorNumberByText,
getPlatformColorDef,
@ -184,92 +184,124 @@
okLabel={isNew ? presentation.string.Create : presentation.string.Save}
okAction={handleSave}
canSave={name.length > 0 && !(members.length === 0 && isPrivate)}
gap={'gapV-4'}
accentHeader
width={'medium'}
gap={'gapV-6'}
on:close={() => {
dispatch('close')
}}
on:changeContent
>
<div class="flex-row-center flex-between">
<EditBox
bind:value={name}
placeholder={tracker.string.ProjectTitlePlaceholder}
kind={'large-style'}
focus
on:input={() => {
if (isNew) {
identifier = name.toLocaleUpperCase().replaceAll(' ', '_').substring(0, 5)
}
}}
/>
<div class="flex-row-center">
<EditBox
bind:value={identifier}
disabled={!isNew}
placeholder={tracker.string.ProjectIdentifierPlaceholder}
kind={'large-style'}
/>
{#if !isNew}
<Button size={'small'} icon={IconEdit} on:click={changeIdentity} />
{/if}
</div>
</div>
<StyledTextBox
alwaysEdit
showButtons={false}
bind:content={description}
placeholder={tracker.string.IssueDescriptionPlaceholder}
/>
<ToggleWithLabel
label={presentation.string.MakePrivate}
description={presentation.string.MakePrivateDescription}
bind:on={isPrivate}
disabled={!isPrivate && members.length === 0}
/>
<div class="flex-between">
<div class="caption">
<Label label={tracker.string.ChooseIcon} />
</div>
<Button
icon={icon === tracker.component.IconWithEmojii ? IconWithEmojii : icon ?? tracker.icon.Home}
iconProps={icon === tracker.component.IconWithEmojii
? { icon: color }
: {
fill:
color !== undefined
? getPlatformColorDef(color, $themeStore.dark).icon
: getPlatformColorForTextDef(name, $themeStore.dark).icon
<div class="antiGrid">
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={tracker.string.ProjectTitle} />
</div>
<div class="padding">
<EditBox
bind:value={name}
placeholder={tracker.string.ProjectTitlePlaceholder}
kind={'large-style'}
focus
on:input={() => {
if (isNew) {
identifier = name.toLocaleUpperCase().replaceAll(' ', '_').substring(0, 5)
}
}}
kind="no-border"
size="medium"
on:click={chooseIcon}
/>
/>
</div>
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header withDesciption">
<Label label={tracker.string.Identifier} />
<span><Label label={tracker.string.UsedInIssueIDs} /></span>
</div>
<div class="padding flex-row-center">
<EditBox
bind:value={identifier}
disabled={!isNew}
placeholder={tracker.string.ProjectIdentifierPlaceholder}
kind={'large-style'}
uppercase
/>
{#if !isNew}
<Button size={'small'} icon={IconEdit} on:click={changeIdentity} />
{/if}
</div>
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header topAlign">
<Label label={tracker.string.Description} />
</div>
<div class="padding clear-mins">
<StyledTextBox
alwaysEdit
showButtons={false}
bind:content={description}
placeholder={tracker.string.IssueDescriptionPlaceholder}
/>
</div>
</div>
</div>
<div class="flex-between">
<div class="caption">
<Label label={tracker.string.Members} />
<div class="antiGrid">
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={tracker.string.ChooseIcon} />
</div>
<Button
icon={icon === tracker.component.IconWithEmojii ? IconWithEmojii : icon ?? tracker.icon.Home}
iconProps={icon === tracker.component.IconWithEmojii
? { icon: color }
: {
fill:
color !== undefined
? getPlatformColorDef(color, $themeStore.dark).icon
: getPlatformColorForTextDef(name, $themeStore.dark).icon
}}
size={'large'}
on:click={chooseIcon}
/>
</div>
<AccountArrayEditor
value={members}
label={tracker.string.Members}
onChange={(refs) => (members = refs)}
kind="link-bordered"
/>
</div>
<div class="flex-between">
<div class="caption">
<Label label={tracker.string.DefaultAssignee} />
<div class="antiGrid-row">
<div class="antiGrid-row__header withDesciption">
<Label label={presentation.string.MakePrivate} />
<span><Label label={presentation.string.MakePrivateDescription} /></span>
</div>
<Toggle bind:on={isPrivate} disabled={!isPrivate && members.length === 0} />
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={tracker.string.Members} />
</div>
<AccountArrayEditor
value={members}
label={tracker.string.Members}
onChange={(refs) => (members = refs)}
kind={'secondary'}
size={'large'}
/>
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={tracker.string.DefaultAssignee} />
</div>
<AssigneeBox
label={tracker.string.Assignee}
placeholder={tracker.string.Assignee}
kind={'secondary'}
size={'large'}
avatarSize={'card'}
bind:value={defaultAssignee}
titleDeselect={tracker.string.Unassigned}
showNavigate={false}
showTooltip={{ label: tracker.string.DefaultAssignee }}
/>
</div>
<AssigneeBox
label={tracker.string.Assignee}
placeholder={tracker.string.Assignee}
kind="link-bordered"
bind:value={defaultAssignee}
titleDeselect={tracker.string.Unassigned}
showNavigate={false}
showTooltip={{ label: tracker.string.DefaultAssignee }}
/>
</div>
</Card>

View File

@ -66,6 +66,7 @@ export default mergeIds(trackerId, tracker, {
Canceled: '' as IntlString,
CreateProject: '' as IntlString,
NewProject: '' as IntlString,
ProjectTitle: '' as IntlString,
ProjectTitlePlaceholder: '' as IntlString,
ProjectIdentifierPlaceholder: '' as IntlString,
ChooseIcon: '' as IntlString,
@ -88,7 +89,9 @@ export default mergeIds(trackerId, tracker, {
Medium: '' as IntlString,
Low: '' as IntlString,
Title: '' as IntlString,
UsedInIssueIDs: '' as IntlString,
Identifier: '' as IntlString,
ProjectIdentifier: '' as IntlString,
IdentifierExists: '' as IntlString,
Description: '' as IntlString,
Status: '' as IntlString,
@ -131,7 +134,6 @@ export default mergeIds(trackerId, tracker, {
Attachments: '' as IntlString,
Labels: '' as IntlString,
Space: '' as IntlString,
NoDueDate: '' as IntlString,
SetDueDate: '' as IntlString,
ChangeDueDate: '' as IntlString,
ModificationDate: '' as IntlString,

View File

@ -140,10 +140,15 @@ test('report-time-from-issue-card', async ({ page }) => {
const random = Math.floor(Math.random() * values.length)
const time = values[random]
const name = getIssueName()
await createIssue(page, { name, assignee, status })
await page.waitForSelector(`text="${name}"`)
await page.waitForSelector('text="View issue"')
await page.click('text="View issue"')
try {
await page.evaluate(() => localStorage.setItem('#platform.notification.timeout', '5000'))
await createIssue(page, { name, assignee, status })
await page.waitForSelector(`text="${name}"`)
await page.waitForSelector('text="View issue"')
await page.click('text="View issue"')
} finally {
await page.evaluate(() => localStorage.setItem('#platform.notification.timeout', '1'))
}
await page.click('#ReportedTimeEditor')
await page.waitForSelector('text="Time spend reports"')
@ -246,7 +251,7 @@ test('create-issue-draft', async ({ page }) => {
await page.locator('.ml-2 > .antiButton').click()
// Click button:has-text("No due date")
await page.locator('button:has-text("No due date")').click()
await page.locator('button:has-text("Due date")').click()
// Click text=24 >> nth=0
await page.locator('.date-popup-container >> text=24').first().click()

View File

@ -83,7 +83,7 @@ export async function fillIssueForm (page: Page, props: IssueProps): Promise<voi
await page.click(`.selectPopup button:has-text("${component}")`)
}
if (milestone !== undefined) {
await page.click(af + '.antiButton:has-text("No Milestone")')
await page.click(af + '.antiButton:has-text("Milestone")')
await page.click(`.selectPopup button:has-text("${milestone}")`)
}
}