UBER-795: updated layout of pop-ups. There is always a Back in the Panel. (#3644)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2023-08-31 08:46:04 +03:00 committed by GitHub
parent e80844e18e
commit 41650925c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 804 additions and 808 deletions

View File

@ -18,7 +18,7 @@
import { Button, IconClose, Label, Scroller } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import presentation from '..'
import { deviceOptionsStore as deviceInfo, resizeObserver } from '@hcengineering/ui'
import { deviceOptionsStore as deviceInfo, resizeObserver, IconBack } from '@hcengineering/ui'
import IconForward from './icons/Forward.svelte'
export let label: IntlString
@ -27,10 +27,16 @@
export let canSave: boolean = false
export let okLabel: IntlString = presentation.string.Create
export let onCancel: Function | undefined = undefined
export let backAction: () => Promise<void> | void = () => {}
export let isBack: boolean = false
export let fullSize: boolean = false
export let hideAttachments: boolean = false
export let hideSubheader: boolean = false
export let numberOfBlocks: number = 0
export let thinHeader: boolean = false
export let accentHeader: boolean = false
export let hideSubheader: boolean = false
export let hideContent: boolean = false
export let hideAttachments: boolean = false
export let hideFooter: boolean = false
export let gap: string | undefined = undefined
export let width: 'large' | 'medium' | 'small' | 'x-small' | 'menu' = 'large'
export let noFade = false
@ -38,30 +44,40 @@
const dispatch = createEventDispatcher()
let okProcessing = false
$: headerDivide = hideContent && numberOfBlocks > 1
</script>
<form
id={label}
class="antiCard {$deviceInfo.isMobile ? 'mobile' : 'dialog'} {width}"
class:full={fullSize}
on:keydown
on:submit|preventDefault={() => {}}
use:resizeObserver={() => {
dispatch('changeContent')
}}
>
<div class="antiCard-header" class:withSub={$$slots.subheader && !hideSubheader}>
<div
class="antiCard-header"
class:withSub={$$slots.subheader && !hideSubheader}
class:thinHeader
class:border-bottom-popup-divider={headerDivide}
>
<div class="antiCard-header__title-wrap">
{#if $$slots.header}
<slot name="header" />
<span class="antiCard-header__divider"><IconForward size={'small'} /></span>
{/if}
<span class="antiCard-header__title" class:accentHeader>
{#if isBack}
<Button icon={IconBack} kind={'ghost'} size={'small'} on:click={backAction} />
{/if}
<div class="antiCard-header__title" class:accentHeader>
{#if $$slots.title}
<slot name="title" {label} labelProps={labelProps ?? {}} />
{:else}
<Label {label} params={labelProps ?? {}} />
{/if}
</span>
</div>
</div>
<div class="ml-4 buttons-group small-gap content-dark-color">
<Button
@ -86,62 +102,81 @@
<slot name="subheader" />
</div>
{/if}
<div class="antiCard-content">
<Scroller padding={$$slots.pool ? '.5rem 1.5rem' : '.5rem 1.5rem 1.5rem'} {gap} {noFade}>
<slot />
</Scroller>
</div>
{#if !hideContent}
<div class="antiCard-content">
<Scroller padding={$$slots.pool ? '.5rem 1.5rem' : '.5rem 1.5rem 1.5rem'} {gap} {noFade}>
<slot />
</Scroller>
</div>
{/if}
{#if $$slots.pool}
<div class="antiCard-pool">
<slot name="pool" />
</div>
{/if}
{#if $$slots.blocks && numberOfBlocks}
{#if numberOfBlocks === 1}
<div class="antiCard-block">
<slot name="blocks" block={0} />
</div>
{:else}
<Scroller noFade={false}>
{#each [...Array(numberOfBlocks).keys()] as block}
<div class="antiCard-blocks" class:border-top-none={headerDivide && block === 0}>
<slot name="blocks" {block} />
</div>
{/each}
</Scroller>
{/if}
{/if}
{#if $$slots.attachments && !hideAttachments}
<div class="antiCard-attachments">
<Scroller horizontal contentDirection={'horizontal'} {gap}>
<Scroller horizontal contentDirection={'horizontal'} {gap} noFade={false}>
<div class="antiCard-attachments__container">
<slot name="attachments" />
</div>
</Scroller>
</div>
{/if}
<div class="antiCard-footer divide reverse">
<div class="buttons-group text-sm flex-no-shrink">
{#if $$slots.buttons}
<slot name="buttons" />
{/if}
<Button
loading={okProcessing}
focusIndex={10001}
disabled={!canSave}
label={okLabel}
kind={'accented'}
size={'large'}
on:click={() => {
if (okProcessing) {
return
}
okProcessing = true
const r = okAction()
if (r instanceof Promise) {
r.then(() => {
{#if !hideFooter}
<div class="antiCard-footer divide reverse">
<div class="buttons-group text-sm flex-no-shrink">
{#if $$slots.buttons}
<slot name="buttons" />
{/if}
<Button
loading={okProcessing}
focusIndex={10001}
disabled={!canSave}
label={okLabel}
kind={'accented'}
size={'large'}
on:click={() => {
if (okProcessing) {
return
}
okProcessing = true
const r = okAction()
if (r instanceof Promise) {
r.then(() => {
okProcessing = false
dispatch('close')
})
} else {
okProcessing = false
dispatch('close')
})
} else {
okProcessing = false
dispatch('close')
}
}}
/>
}
}}
/>
</div>
<div class="buttons-group small-gap text-sm">
<slot name="footer" />
{#if $$slots.error}
<div class="antiCard-footer__error">
<slot name="error" />
</div>
{/if}
</div>
</div>
<div class="buttons-group small-gap text-sm">
<slot name="footer" />
{#if $$slots.error}
<div class="antiCard-footer__error">
<slot name="error" />
</div>
{/if}
</div>
</div>
{/if}
</form>

View File

@ -1,7 +1,6 @@
<script lang="ts">
import { DocIndexState } from '@hcengineering/core'
import { EditBox, Panel } from '@hcengineering/ui'
import { EditBox, Dialog } from '@hcengineering/ui'
import IndexedDocumentContent from './IndexedDocumentContent.svelte'
export let left: DocIndexState
@ -10,46 +9,25 @@
let search: string = ''
</script>
<Panel on:changeContent on:close>
<EditBox autoFocus bind:value={search} kind="search-style" />
<div class="indexed-background">
<div class="indexed-doc text-base max-h-125">
<div class="flex">
<div class="indexed-doc-part">
<IndexedDocumentContent indexDoc={left} {search} />
</div>
{#if right !== undefined}
<div class="indexed-doc-part">
<IndexedDocumentContent indexDoc={right} {search} />
</div>
{/if}
</div>
<Dialog isFullSize on:changeContent on:close on:fullsize>
<EditBox autoFocus bind:value={search} kind={'default-large'} fullSize />
<div class="flex-row-top px-3 mt-4 text-base">
<div class="indexed-doc-part flex-col">
<IndexedDocumentContent indexDoc={left} {search} />
</div>
{#if right !== undefined}
<div class="indexed-doc-part flex-col ml-4">
<IndexedDocumentContent indexDoc={right} {search} />
</div>
{/if}
</div>
</Panel>
</Dialog>
<style lang="scss">
.indexed-doc {
padding: 2.5rem;
display: flex;
overflow: auto;
min-width: 50rem;
max-width: 100rem;
}
.indexed-doc-part {
padding: 0.5rem;
display: grid;
overflow: auto;
min-width: 25rem;
max-width: 50rem;
}
.indexed-background {
background-color: white;
color: black;
user-select: text;
// width: 200rem;
.highlight {
color: blue;
}
flex-basis: 100%;
height: fit-content;
max-height: 100%;
}
</style>

View File

@ -45,7 +45,7 @@
{#if summary}
{#if search.length > 0}
Result:
<span class="font-medium">Result:</span>
{#each summary.split('\n').filter((line, idx, arr) => {
return line.toLowerCase().includes(search.toLowerCase()) || arr[idx - 1]
?.toLowerCase()
@ -55,7 +55,7 @@
{/each}
<br />
{/if}
Summary:
<span class="font-medium">Summary:</span>
{#each summary.split('\n') as line}
{@const hl = search.length > 0 && line.toLowerCase().includes(search.toLowerCase())}
<span class:text-md={!hl} class:highlight={hl}>{line}</span>
@ -75,7 +75,7 @@
{#each attr[1] as doc}
<div class="p-1" class:flex-col={doc.length > 1}>
{#if search.length > 0}
Result:
<span class="font-medium">Result:</span>
{#each doc.filter((line) => line.toLowerCase().includes(search.toLowerCase())) as line}
<span class:highlight={true}>{line}</span>
{/each}
@ -93,6 +93,6 @@
<style lang="scss">
.highlight {
color: blue;
color: var(--theme-link-color);
}
</style>

View File

@ -1,7 +1,7 @@
<script lang="ts">
import core, { Doc, DocIndexState, Ref } from '@hcengineering/core'
import { EditBox, Label, Panel } from '@hcengineering/ui'
import { EditBox, Label, Dialog } from '@hcengineering/ui'
import { createQuery } from '../utils'
import IndexedDocumentContent from './IndexedDocumentContent.svelte'
import presentation from '../plugin'
@ -25,39 +25,29 @@
{#if noPanel}
<div class="p-1 flex-col">
<Label label={presentation.string.DocumentPreview} />
<EditBox autoFocus bind:value={search} kind="search-style" />
<EditBox autoFocus bind:value={search} kind={'default-large'} fullSize />
</div>
<div class="indexed-background">
<div class="indexed-doc text-base max-h-125">
<div class="indexed-doc flex-col px-3 mt-4 text-base">
{#if indexDoc}
<IndexedDocumentContent {indexDoc} {search} />
{/if}
</div>
{:else}
<Dialog isFullSize on:changeContent on:close on:fullsize>
<EditBox autoFocus bind:value={search} kind={'default-large'} fullSize />
<div class="indexed-doc flex-col px-3 mt-4 text-base">
{#if indexDoc}
<IndexedDocumentContent {indexDoc} {search} />
{/if}
</div>
</div>
{:else}
<Panel on:changeContent on:close>
<EditBox autoFocus bind:value={search} kind="search-style" />
<div class="indexed-background">
<div class="indexed-doc text-base max-h-125">
{#if indexDoc}
<IndexedDocumentContent {indexDoc} {search} />
{/if}
</div>
</div>
</Panel>
</Dialog>
{/if}
<style lang="scss">
.indexed-doc {
padding: 2.5rem;
display: grid;
overflow: auto;
min-width: 50rem;
max-width: 80rem;
}
.indexed-background {
background-color: white;
color: black;
user-select: text;
flex-basis: 100%;
height: fit-content;
max-height: 100%;
}
</style>

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Doc } from '@hcengineering/core'
import { Button, deviceOptionsStore as deviceInfo, Panel, PopupOptions } from '@hcengineering/ui'
import { Button, Dialog, PopupOptions } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import presentation from '..'
import { getFileUrl } from '../utils'
@ -42,9 +42,7 @@
</script>
<ActionContext context={{ mode: 'browser' }} />
<Panel
isHeader={false}
isAside={popupOptions && popupOptions.fullSize}
<Dialog
isFullSize
on:fullsize
on:close={() => {
@ -76,19 +74,13 @@
</svelte:fragment>
{#if contentType && contentType.startsWith('image/')}
<div class="pdfviewer-content img" style:margin={$deviceInfo.minWidth ? '.5rem' : '1.5rem'}>
<div class="pdfviewer-content img">
<img class="img-fit" src={getFileUrl(file, 'full', name)} alt="" />
</div>
<div class="space" />
{:else}
<iframe
class="pdfviewer-content"
style:margin={$deviceInfo.minWidth ? '.5rem' : '1.5rem'}
src={getFileUrl(file, 'full', name) + '#view=FitH&navpanes=0'}
title=""
/>
<iframe class="pdfviewer-content" src={getFileUrl(file, 'full', name) + '#view=FitH&navpanes=0'} title="" />
{/if}
</Panel>
</Dialog>
<style lang="scss">
.icon {

View File

@ -22,6 +22,7 @@
Button,
ButtonKind,
ButtonSize,
ButtonShape,
eventToHTMLElement,
getEventPositionElement,
getFocusManager,
@ -50,6 +51,7 @@
export let size: ButtonSize = 'large'
export let itemSize: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let shape: ButtonShape = undefined
export let width: string | undefined = undefined
export let allowDeselect = false
export let component: AnySvelteComponent | undefined = undefined
@ -119,6 +121,7 @@
<Button
id="space.selector"
{focus}
{shape}
disabled={readonly}
{focusIndex}
icon={selected?.icon === iconWithEmoji && iconWithEmoji ? IconWithEmoji : selected?.icon ?? defaultIcon}

View File

@ -15,7 +15,7 @@
<script lang="ts">
import { Class, DocumentQuery, FindOptions, Ref, Space } from '@hcengineering/core'
import { Asset, IntlString } from '@hcengineering/platform'
import { AnySvelteComponent, ButtonKind, ButtonSize } from '@hcengineering/ui'
import { AnySvelteComponent, ButtonKind, ButtonSize, ButtonShape } from '@hcengineering/ui'
import { ObjectCreate } from '../types'
import SpaceSelect from './SpaceSelect.svelte'
import { createEventDispatcher } from 'svelte'
@ -28,6 +28,7 @@
export let label: IntlString
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let shape: ButtonShape = undefined
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let allowDeselect = false
@ -54,6 +55,7 @@
{label}
{size}
{kind}
{shape}
{justify}
{width}
{component}

View File

@ -108,6 +108,7 @@
// Be aware to update defineAlpha() function in colors.ts
--theme-bg-color: #1A1A28;
--theme-bg-accent-color: rgba(0, 0, 0, .08);
--theme-bg-dark-color: rgba(0, 0, 0, .2);
--theme-back-color: #0f0f18;
--theme-overlay-color: rgba(0, 0, 0, .3);
--theme-statusbar-color: #2C2C35;
@ -303,6 +304,7 @@
// Be aware to update defineAlpha() function in colors.ts
--theme-bg-color: #F1F1F4;
--theme-bg-accent-color: rgba(255, 255, 255, .08);
--theme-bg-dark-color: rgba(255, 255, 255, .8);
--theme-back-color: #D9D9DD;
--theme-overlay-color: rgba(0, 0, 0, .2);
--theme-statusbar-color: #bfbfc6;

View File

@ -225,6 +225,7 @@ input.search {
align-items: flex-start;
flex-wrap: nowrap;
min-width: 0;
min-height: 0;
}
.flex-row-reverse {
display: flex;
@ -683,6 +684,8 @@ input.search {
.min-h-5 { min-height: 1.25rem; }
.min-h-7 { min-height: 1.75rem; }
.min-h-8 { min-height: 2rem; }
.min-h-9 { min-height: 2.25rem; }
.min-h-11 { min-height: 2.75rem; }
.min-h-30 { min-height: 7.5rem; }
.min-h-60 { min-height: 15rem; }
.max-w-2 { max-width: .5rem; }
@ -819,6 +822,7 @@ a.no-line {
.text-left { text-align: left; }
.text-center { text-align: center; }
.leading-3 { line-height: .75rem; }
.tracking-1px { letter-spacing: 1px; }
.over-underline {
cursor: pointer;
@ -944,6 +948,9 @@ a.no-line {
.border-divider-color {border: 1px solid var(--theme-divider-color);}
.border-primary-button { border-color: var(--accented-button-border); }
.border-top-none { border-top: none !important; }
.border-bottom-popup-divider { border-bottom: 1px solid var(--theme-popup-divider); }
.top-divider { border-top: 1px solid var(--theme-divider-color); }
.bottom-divider { border-bottom: 1px solid var(--theme-divider-color); }
.left-divider { border-left: 1px solid var(--theme-divider-color); }

View File

@ -40,8 +40,10 @@
align-items: center;
flex-shrink: 0;
&.withSub { padding: 1.5rem 1.5rem 0; }
&:not(.withSub) { padding: 1.5rem 1.5rem 1rem; }
&.withSub:not(.thinHeader) { padding: 1.5rem 1.5rem 0; }
&.withSub.thinHeader { padding: 1rem 1.5rem 0; }
&.thinHeader:not(.withSub) { padding: 1rem 1.5rem; }
&:not(.withSub, .thinHeader) { padding: 1.5rem; }
&__title-wrap {
display: flex;
@ -81,7 +83,7 @@
display: flex;
align-items: center;
flex-shrink: 0;
padding: .5rem 1.5rem 1rem;
padding: .5rem 1.5rem 1.5rem;
min-width: 0;
min-height: 0;
}
@ -125,10 +127,23 @@
}
}
.antiCard-attachments {
background-color: var(--theme-bg-accent-color);
border-top: 1px solid var(--theme-popup-divider);
.antiCard-attachments,
.antiCard-block,
.antiCard-blocks { border-top: 1px solid var(--theme-popup-divider); }
.antiCard-block,
.antiCard-blocks {
display: flex;
flex-direction: column;
height: fit-content;
min-width: 0;
min-height: 0;
}
.antiCard-attachments,
.antiCard-blocks { background-color: var(--theme-bg-accent-color); }
.antiCard-attachments {
&__container {
display: flex;
align-items: center;
@ -138,6 +153,8 @@
& > *:last-child { margin-right: 1.5rem; }
}
}
.antiCard-block { padding: 1.5rem; }
.antiCard-blocks { padding: .75rem 1.5rem; }
.antiCard-footer {
overflow: hidden;

View File

@ -178,7 +178,7 @@
margin-left: auto;
margin-right: auto;
width: calc(100% - 7.5rem);
max-width: 860px;
max-width: 54rem;
&.max {
max-width: 100%;
@ -337,10 +337,9 @@
.popupPanel-title,
.popupPanel-body {
border-radius: 0;
// border-radius: 0;
border: none;
}
// .popupPanel-title { border-bottom: 1px solid var(--divider-color); }
}
.popup.fullsize {
transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19) !important;

View File

@ -15,111 +15,128 @@
-->
<script lang="ts">
import type { IntlString } from '@hcengineering/platform'
import { createEventDispatcher } from 'svelte'
import { resizeObserver, Button, Label, IconClose, IconScale, IconScaleFull } from '..'
import Close from './icons/Close.svelte'
import ScrollBox from './ScrollBox.svelte'
import Button from './Button.svelte'
import Label from './Label.svelte'
import ui from '../plugin'
export let label: IntlString
export let okLabel: IntlString
export let okAction: () => void
export let label: IntlString | undefined = undefined
export let isFullSize: boolean = false
const dispatch = createEventDispatcher()
let fullSize: boolean = false
</script>
<div class="dialog-container">
<form
class="dialog"
on:submit|preventDefault={() => {
okAction()
dispatch('close')
}}
>
<div class="flex-between header">
<div class="title"><Label {label} /></div>
<div
class="tool"
on:click={() => {
dispatch('close')
}}
>
<Close size={'small'} />
<form
class="dialog-container"
class:fullsize={fullSize}
on:submit|preventDefault={() => {}}
use:resizeObserver={() => {
dispatch('changeContent')
}}
>
<div class="flex-between header">
<div class="flex-row-center gap-1-5">
<Button icon={IconClose} kind={'ghost'} size={'medium'} on:click={() => dispatch('close')} />
<div class="title">
{#if label}<Label {label} />{/if}
{#if $$slots.title}<slot name="title" />{/if}
</div>
</div>
<div class="content">
<ScrollBox vertical stretch><slot /></ScrollBox>
<div class="flex-row-center gap-1-5">
{#if $$slots.utils}
<slot name="utils" />
{/if}
{#if $$slots.utils && isFullSize}
<div class="buttons-divider" />
{/if}
{#if isFullSize}
<Button
focusIndex={100010}
icon={fullSize ? IconScale : IconScaleFull}
kind={'ghost'}
size={'medium'}
selected={fullSize}
on:click={() => {
fullSize = !fullSize
dispatch('fullsize')
}}
/>
{/if}
</div>
</div>
<div class="content" class:rounded={!($$slots.footerLeft || $$slots.footerRight)}>
<slot />
</div>
{#if $$slots.footerLeft || $$slots.footerRight}
<div class="footer">
<Button label={okLabel} kind={'accented'} />
<Button
label={ui.string.Cancel}
on:click={() => {
dispatch('close')
}}
/>
{#if $$slots.footerLeft}
<div class="flex-row-center gap-2">
<slot name="footerLeft" />
</div>
{:else}<div />{/if}
{#if $$slots.footerRight}
<div class="flex-row-center gap-2">
<slot name="footerRight" />
</div>
{:else}<div />{/if}
</div>
</form>
</div>
{/if}
</form>
<style lang="scss">
.dialog {
.dialog-container {
display: flex;
flex-direction: column;
width: 45rem;
height: 100vh;
min-height: 100vh;
max-height: 100vh;
background-color: var(--theme-bg-color);
border-radius: 1.875rem 0 0 1.875rem;
box-shadow: 0px 3.125rem 7.5rem rgba(0, 0, 0, 0.4);
min-width: 25rem;
max-width: calc(100vw - 2rem);
min-height: 0;
max-height: 80vh;
background-color: var(--theme-popup-color);
border-radius: 0.5rem;
&:not(.fullsize) {
border: 1px solid var(--theme-popup-divider);
box-shadow: var(--theme-popup-shadow);
}
&.fullsize {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
}
.header {
flex-shrink: 0;
padding: 0 2rem 0 2.5rem;
height: 4.5rem;
padding: 0.5rem;
background-color: var(--theme-popup-header);
border-bottom: 1px solid var(--theme-popup-divider);
border-radius: 0.5rem 0.5rem 0 0;
.title {
flex-grow: 1;
font-weight: 500;
font-size: 1.125rem;
color: var(--caption-color);
user-select: none;
}
.tool {
margin-left: 0.75rem;
opacity: 0.4;
cursor: pointer;
&:hover {
opacity: 1;
}
font-size: 1rem;
color: var(--theme-caption-color);
}
}
.content {
flex-shrink: 0;
flex-grow: 1;
margin: 0 2.5rem;
height: max-content;
}
display: flex;
flex-direction: column;
padding: 1rem;
min-width: 0;
min-height: 0;
&.rounded {
border-radius: 0 0 0.5rem 0.5rem;
}
}
.footer {
flex-shrink: 0;
display: grid;
grid-auto-flow: column;
direction: rtl;
justify-content: start;
display: flex;
justify-content: space-between;
align-items: center;
column-gap: 0.75rem;
padding: 0 2.5rem;
height: 6rem;
mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0) 1.25rem, rgba(0, 0, 0, 1) 2.5rem);
overflow: hidden;
flex-shrink: 0;
padding: 0.25rem 0.5rem;
border-top: 1px solid var(--theme-popup-divider);
border-radius: 0 0 0.5rem 0.5rem;
}
}
</style>

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
import { deviceOptionsStore as deviceInfo, checkAdaptiveMatching, embeddedPlatform, IconBack } from '../../'
import { deviceOptionsStore as deviceInfo, checkAdaptiveMatching, IconBack } from '../../'
import { resizeObserver } from '../resize'
import Button from './Button.svelte'
import Scroller from './Scroller.svelte'
@ -96,20 +96,19 @@
class:embedded
>
<div class="popupPanel-title {twoRows && !withoutTitle ? 'row-top' : 'row'}">
{#if allowClose && !embedded}
{#if embeddedPlatform}
<Button
focusIndex={10000}
icon={IconBack}
kind={'ghost'}
size={'medium'}
on:click={() => {
history.back()
}}
/>
{/if}
<Button
focusIndex={10000}
icon={IconBack}
kind={'ghost'}
size={'medium'}
on:click={() => {
history.back()
}}
/>
{#if allowClose}
<div class="antiHSpacer" />
<Button
focusIndex={10000}
focusIndex={10001}
icon={IconClose}
kind={'ghost'}
size={'medium'}

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
export let fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">

View File

@ -1,16 +1,43 @@
<!--
// Copyright © 2023 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">
const fill: string = 'var(--caption-color)'
export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script>
<svg {fill} viewBox="0 0 6 16" xmlns="http://www.w3.org/2000/svg">
<circle cx="1" cy="1" r="1" />
<circle cx="4.5" cy="1" r="1" />
<circle cx="1" cy="4.5" r="1" />
<circle cx="4.5" cy="4.5" r="1" />
<circle cx="1" cy="8" r="1" />
<circle cx="4.5" cy="8" r="1" />
<circle cx="1" cy="11.5" r="1" />
<circle cx="4.5" cy="11.5" r="1" />
<circle cx="1" cy="15" r="1" />
<circle cx="4.5" cy="15" r="1" />
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path
d="M9 5.99988C9 6.55216 9.44772 6.99988 10 6.99988C10.5523 6.99988 11 6.55216 11 5.99988C11 5.44759 10.5523 4.99988 10 4.99988C9.44772 4.99988 9 5.44759 9 5.99988Z"
/>
<path
d="M9 9.99988C9 10.5522 9.44772 10.9999 10 10.9999C10.5523 10.9999 11 10.5522 11 9.99988C11 9.44759 10.5523 8.99988 10 8.99988C9.44772 8.99988 9 9.44759 9 9.99988Z"
/>
<path
d="M10 14.9999C9.44772 14.9999 9 14.5522 9 13.9999C9 13.4476 9.44772 12.9999 10 12.9999C10.5523 12.9999 11 13.4476 11 13.9999C11 14.5522 10.5523 14.9999 10 14.9999Z"
/>
<path
d="M9 2C9 2.55228 9.44772 3 10 3C10.5523 3 11 2.55228 11 2C11 1.44772 10.5523 1 10 1C9.44772 1 9 1.44772 9 2Z"
/>
<path
d="M5 5.99988C5 6.55216 5.44772 6.99988 6 6.99988C6.55229 6.99988 7 6.55216 7 5.99988C7 5.44759 6.55229 4.99988 6 4.99988C5.44772 4.99988 5 5.44759 5 5.99988Z"
/>
<path
d="M5 9.99988C5 10.5522 5.44772 10.9999 6 10.9999C6.55228 10.9999 7 10.5522 7 9.99988C7 9.44759 6.55229 8.99988 6 8.99988C5.44772 8.99988 5 9.44759 5 9.99988Z"
/>
<path
d="M6 14.9999C5.44772 14.9999 5 14.5522 5 13.9999C5 13.4476 5.44772 12.9999 6 12.9999C6.55228 12.9999 7 13.4476 7 13.9999C7 14.5522 6.55228 14.9999 6 14.9999Z"
/>
<path d="M5 2C5 2.55228 5.44772 3 6 3C6.55229 3 7 2.55228 7 2C7 1.44772 6.55229 1 6 1C5.44772 1 5 1.44772 5 2Z" />
</svg>

View File

@ -296,7 +296,7 @@ export function fitPopupElement (
show = false
} else if (element === 'full' && contentPanel !== undefined) {
const rect = contentPanel.getBoundingClientRect()
newProps.top = `${rect.top + 1}px`
newProps.top = `${rect.top + 4}px`
newProps.bottom = '4px'
newProps.left = '4px'
newProps.right = '4px'

View File

@ -140,7 +140,7 @@
)
}
async function showSummary (left: DocIndexState, right?: DocIndexState): Promise<void> {
showPopup(IndexedDocumentCompare, { left, right }, 'top')
showPopup(IndexedDocumentCompare, { left, right }, 'centered')
}
</script>

View File

@ -173,13 +173,11 @@
}}
on:changeContent
>
<div on:keydown={onKeydown}>
<div class="mb-2">
<EditBox bind:value={name} placeholder={core.string.Name} />
</div>
<div class="flex-between mb-2">
<EditBox placeholder={presentation.string.Search} kind="large-style" bind:value={newValue} />
<div class="flex gap-2">
<div class="flex-col" on:keydown={onKeydown}>
<EditBox bind:value={name} placeholder={core.string.Name} kind={'large-style'} fullSize />
<div class="flex-between my-4">
<EditBox placeholder={presentation.string.Search} kind={'large-style'} bind:value={newValue} fullSize />
<div class="flex-row-center flex-no-shrink gap-2 ml-4">
<ActionIcon icon={IconAdd} label={presentation.string.Add} action={add} size={'small'} />
<ActionIcon
icon={Copy}
@ -207,7 +205,7 @@
</div>
<svelte:fragment slot="footer">
<div
class="resume flex gap-2"
class="resume flex-center"
class:solid={dragover}
on:dragover|preventDefault={() => {
dragover = true

View File

@ -123,7 +123,7 @@
kind="large-style"
bind:value={newValue}
/>
<div class="flex gap-2">
<div class="flex-row-center gap-2">
<ActionIcon
icon={IconAdd}
label={setting.string.Add}
@ -131,7 +131,6 @@
size={'small'}
disabled={value.enumValues.includes(newValue.trim())}
/>
<ActionIcon
icon={IconAttachment}
label={setting.string.ImportEnum}

View File

@ -57,7 +57,7 @@
<Scroller>
{#each filtered as item, i}
<div
class="flex-between flex-nowrap item mb-2"
class="flex-between flex-nowrap item step-tb25"
draggable={true}
bind:this={elements[i]}
on:dragover|preventDefault={(ev) => {
@ -71,9 +71,9 @@
selected = undefined
}}
>
<div class="flex">
<div class="circles-mark"><IconCircles /></div>
<span class="overflow-label">{item}</span>
<div class="flex-row-center">
<div class="circles-mark"><IconCircles size={'small'} /></div>
<span class="overflow-label mx-2">{item}</span>
</div>
<ActionIcon
icon={IconDelete}
@ -85,6 +85,7 @@
/>
</div>
{/each}
{#if filtered.length}<div class="antiVSpacer x4" />{/if}
</Scroller>
{#if filtered.length === 0}
<Label label={presentation.string.NoMatchesFound} />
@ -92,26 +93,35 @@
<style lang="scss">
.item {
padding: 0.5rem 0.5rem 0.5rem 0.125rem;
height: 2.25rem;
background-color: var(--theme-button-default);
border-radius: 0.25rem;
.circles-mark {
position: relative;
width: 1rem;
height: 1rem;
opacity: 0.4;
transition: opacity 0.1s;
cursor: grab;
&::before {
position: absolute;
content: '';
inset: -0.5rem;
}
}
&:hover {
background-color: var(--theme-button-hovered);
.circles-mark {
cursor: grab;
opacity: 1;
}
}
}
.circles-mark {
position: relative;
opacity: 0.4;
width: 0.375rem;
height: 1rem;
transition: opacity 0.1s;
margin-right: 0.5rem;
cursor: grab;
&::before {
position: absolute;
content: '';
inset: -0.5rem;
&:active {
background-color: var(--theme-button-pressed);
}
}
</style>

View File

@ -110,7 +110,7 @@
}}
/>
</div>
<div class="mt-3">
<div class="flex-col mt-3">
{#each states as state, i}
{@const color = getPlatformColorDef(state.color ?? getColorNumberByText(state.name), $themeStore.dark)}
{#if state}
@ -133,7 +133,7 @@
selected = undefined
}}
>
<div class="bar"><IconCircles /></div>
<div class="bar"><IconCircles size={'small'} /></div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="color"
@ -169,7 +169,7 @@
{/if}
{/each}
</div>
<div class="mt-9">
<div class="flex-col mt-9">
<div class="flex-no-shrink flex-between trans-title uppercase">
<Label label={task.string.DoneStatesWon} />
<CircleButton
@ -188,7 +188,7 @@
}}
/>
</div>
<div class="mt-4">
<div class="flex-col mt-4">
{#each wonStates as state}
{@const color = getPlatformColorDef(PaletteColorIndexes.Crocodile, $themeStore.dark)}
{#if state}
@ -294,7 +294,7 @@
<style lang="scss">
.states {
padding: 0.5rem 1rem;
padding: 0.5rem 1rem 0.5rem 0.25rem;
color: var(--theme-caption-color);
background-color: var(--theme-button-default);
border: 1px solid var(--theme-button-border);
@ -302,8 +302,8 @@
user-select: none;
.bar {
margin-right: 0.375rem;
width: 0.375rem;
margin-right: 0.25rem;
width: 1rem;
height: 1rem;
opacity: 0.4;
cursor: grabbing;

View File

@ -151,6 +151,7 @@
"CreatedOne": "Created",
"MoveIssues": "Move issues",
"MoveIssuesDescription": "Select the project you want to move issues to",
"ManageAttributes": "Manage attributes",
"KeepOriginalAttributes": "Keep original attributes",
"KeepOriginalAttributesTooltip": "Original issue statuses and components will be kept in the new project",
"SelectReplacement": "The following items are not available in the new project. Select a replacement.",

View File

@ -151,6 +151,7 @@
"CreatedOne": "Создана",
"MoveIssues": "Переместить задачи",
"MoveIssuesDescription": "Выберите проект, в который вы хотите переместить задачи",
"ManageAttributes": "Управление атрибутами",
"KeepOriginalAttributes": "Оставить оригинальные аттрибуты",
"KeepOriginalAttributesTooltip": "Оригинальные статусы и компоненты будут сохранены в новом проекте",
"SelectReplacement": "Следующие элементы не доступны в новом проекте. Выберите замену.",

View File

@ -25,7 +25,7 @@
export let disabled = false
export let onClick: (() => void) | undefined = undefined
export let shouldShowAvatar: boolean = false
export let noUnderline = false
export let noUnderline = disabled
export let inline = false
export let kind: 'list' | undefined = undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
@ -58,7 +58,7 @@
component={tracker.component.EditIssue}
shrink={0}
>
<span class="issuePresenterRoot" class:inline class:list={kind === 'list'}>
<span class="issuePresenterRoot" class:inline class:list={kind === 'list'} class:cursor-pointer={!disabled}>
{#if !inline && shouldShowAvatar}
<div class="icon" use:tooltip={{ label: tracker.string.Issue }}>
<Icon icon={icon ?? tracker.icon.Issues} size={'small'} />
@ -81,7 +81,6 @@
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
&:not(.list) {
color: var(--theme-content-color);

View File

@ -17,12 +17,11 @@
import { createEventDispatcher } from 'svelte'
import { Ref, Status } from '@hcengineering/core'
import { SpaceSelector, createQuery, getClient } from '@hcengineering/presentation'
import { SpaceSelector, createQuery, getClient, Card } from '@hcengineering/presentation'
import { Component, Issue, IssueStatus, Milestone, Project } from '@hcengineering/tracker'
import ui, { Button, IconClose, Label, Spinner, Toggle, tooltip } from '@hcengineering/ui'
import ui, { Button, IconForward, Label, Spinner, Toggle, tooltip } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { statusStore } from '@hcengineering/view-resources'
import { getEmbeddedLabel } from '@hcengineering/platform'
import tracker from '../../plugin'
import {
@ -265,198 +264,140 @@
}
</script>
<div class="container">
{#if !showManageAttributes}
<div class="space-between">
<span class="fs-title aligned-text">
<Label label={tracker.string.MoveIssues} />
<Card
label={showManageAttributes ? tracker.string.ManageAttributes : tracker.string.MoveIssues}
okLabel={view.string.Move}
okAction={moveAll}
canSave={docs[0]?.space !== currentSpace?._id}
onCancel={() => dispatch('close')}
backAction={() => {
showManageAttributes = !showManageAttributes
}}
isBack={showManageAttributes}
thinHeader
accentHeader
hideSubheader={showManageAttributes}
hideContent={showManageAttributes}
hideAttachments
numberOfBlocks={showManageAttributes
? toMove.length
: currentSpace !== undefined && !keepOriginalAttribytes && docs[0]?.space !== currentSpace?._id
? 1
: 0}
on:changeContent
>
<svelte:fragment slot="title" let:label>
{#if !showManageAttributes}
<Label {label} />
{#if Array.isArray(selected) && selected.length}
<span class="content-dark-color ml-1-5">{selected.length}</span>
{/if}
{:else}
<Label {label} />
{/if}
</svelte:fragment>
<svelte:fragment slot="subheader">
{#if !showManageAttributes}
<span class="content-halfcontent-color overflow-label" style:margin-top={'-.5rem'}>
<Label label={tracker.string.MoveIssuesDescription} />
</span>
<Button icon={IconClose} iconProps={{ size: 'medium' }} kind="ghost" on:click={() => dispatch('close')} />
</div>
{/if}
</svelte:fragment>
<div>
<Label label={tracker.string.MoveIssuesDescription} />
</div>
<div class="space-between mt-6 mb-4">
{#if !showManageAttributes}
<div class="flex-between">
{#if currentSpace !== undefined}
<SpaceSelector
_class={currentSpace._class}
label={hierarchy.getClass(tracker.class.Project).label}
bind:space
kind={'regular'}
size={'small'}
size={'large'}
component={ProjectPresenter}
iconWithEmoji={tracker.component.IconWithEmoji}
defaultIcon={tracker.icon.Home}
/>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="aligned-text"
class:disabled={!isManageAttributesAvailable}
on:click|stopPropagation={() => {
<Button
label={tracker.string.ManageAttributes}
iconRight={IconForward}
kind={'ghost'}
size={'small'}
padding={'0 1rem'}
disabled={!isManageAttributesAvailable}
on:click={() => {
if (!isManageAttributesAvailable) {
return
}
showManageAttributes = !showManageAttributes
}}
>
Manage attributes >
</span>
/>
{/if}
</div>
{:else if loading}<Spinner />{/if}
<div class="divider" />
{#if currentSpace !== undefined && !keepOriginalAttribytes}
<SelectReplacement
{statuses}
{components}
targetProject={currentSpace}
issues={toMove}
bind:statusToUpdate
bind:componentToUpdate
/>
<div class="divider" />
{/if}
{:else}
<div class="space-between pb-4">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="fs-title aligned-text"
on:click|stopPropagation={() => (showManageAttributes = !showManageAttributes)}
>
<Label label={getEmbeddedLabel('< Manage attributes')} />
</span>
<Button icon={IconClose} iconProps={{ size: 'medium' }} kind="ghost" on:click={() => dispatch('close')} />
</div>
<div class="divider" />
<div class="issues-move flex-col">
{#if loading}
<Spinner />
{:else if toMove.length > 0 && currentSpace}
{#each toMove as issue}
{@const upd = issueToUpdate.get(issue._id) ?? {}}
{@const originalComponent = components.find((it) => it._id === issue.component)}
{@const targetComponent = components.find(
(it) => it.space === currentSpace?._id && it.label === originalComponent?.label
)}
{#key keepOriginalAttribytes}
{#if issue.space !== currentSpace._id && (upd.status !== undefined || upd.component !== undefined)}
<div class="issue-move pb-2">
<div class="flex-row-center pl-1">
<PriorityEditor value={issue} isEditable={false} />
<IssuePresenter value={issue} disabled kind={'list'} />
<div class="ml-2 max-w-30">
<TitlePresenter disabled value={issue} showParent={false} />
</div>
</div>
<div class="pl-4">
{#key upd.status}
<StatusMovePresenter {issue} bind:issueToUpdate targetProject={currentSpace} {statuses} />
{/key}
{#if targetComponent === undefined}
{#key upd.component}
<ComponentMovePresenter {issue} bind:issueToUpdate targetProject={currentSpace} {components} />
{/key}
{/if}
</div>
</div>
{/if}
<svelte:fragment slot="blocks" let:block>
{#if !showManageAttributes}
{#if currentSpace !== undefined && !keepOriginalAttribytes}
<SelectReplacement
{statuses}
{components}
targetProject={currentSpace}
issues={toMove}
bind:statusToUpdate
bind:componentToUpdate
/>
{/if}
{:else if toMove.length > 0 && currentSpace}
{@const issue = toMove[block]}
{@const upd = issueToUpdate.get(issue._id) ?? {}}
{@const originalComponent = components.find((it) => it._id === issue.component)}
{@const targetComponent = components.find(
(it) => it.space === currentSpace?._id && it.label === originalComponent?.label
)}
{#key keepOriginalAttribytes}
{#if issue.space !== currentSpace._id && (upd.status !== undefined || upd.component !== undefined)}
<div class="flex-row-center min-h-9 gap-1-5 content-color">
<PriorityEditor value={issue} isEditable={false} kind={'list'} size={'small'} shouldShowLabel={false} />
<IssuePresenter value={issue} disabled kind={'list'} />
<TitlePresenter disabled value={issue} showParent={false} maxWidth={'7.5rem'} />
</div>
{#key upd.status}
<StatusMovePresenter {issue} bind:issueToUpdate targetProject={currentSpace} {statuses} />
{/key}
{/each}
{/if}
</div>
{/if}
{#if targetComponent === undefined}
{#key upd.component}
<ComponentMovePresenter {issue} bind:issueToUpdate targetProject={currentSpace} {components} />
{/key}
{/if}
{/if}
{/key}
{/if}
</svelte:fragment>
<div class="space-between mt-4">
<svelte:fragment slot="footer">
<div
class="aligned-text"
class="flex-row-center gap-2"
use:tooltip={{
component: Label,
props: { label: tracker.string.KeepOriginalAttributesTooltip }
}}
>
<div class="mr-2">
<Toggle
disabled={!isManageAttributesAvailable}
on:change={() => {
keepOriginalAttribytes = !keepOriginalAttribytes
if (!keepOriginalAttribytes) {
statusToUpdate = {}
componentToUpdate = {}
}
}}
/>
</div>
<Label label={tracker.string.KeepOriginalAttributes} />
</div>
<div class="buttons">
<Button
label={view.string.Move}
size={'small'}
disabled={docs[0]?.space === currentSpace?._id}
kind={'accented'}
on:click={moveAll}
loading={processing}
/>
<Button
size={'small'}
label={ui.string.Cancel}
on:click={() => {
dispatch('close')
<Toggle
disabled={!isManageAttributesAvailable}
on:change={() => {
keepOriginalAttribytes = !keepOriginalAttribytes
if (!keepOriginalAttribytes) {
statusToUpdate = {}
componentToUpdate = {}
}
}}
disabled={processing}
/>
<span class="lines-limit-2">
<Label label={tracker.string.KeepOriginalAttributes} />
</span>
</div>
</div>
</div>
<style lang="scss">
.container {
display: flex;
flex-direction: column;
padding: 1.25rem 1.5rem 1rem;
width: 480px;
max-width: 40rem;
background: var(--popup-bg-color);
border-radius: 8px;
user-select: none;
.aligned-text {
display: flex;
justify-content: center;
align-items: center;
}
.space-between {
display: flex;
justify-content: space-between;
}
.buttons {
flex-shrink: 0;
display: grid;
grid-auto-flow: column;
direction: rtl;
justify-content: start;
align-items: center;
column-gap: 0.5rem;
}
.issues-move {
overflow: auto;
}
.issue-move {
border-bottom: 1px solid var(--popup-divider);
}
}
.divider {
border-bottom: 1px solid var(--theme-divider-color);
}
.disabled {
cursor: not-allowed;
color: var(--dark-color);
}
</style>
</svelte:fragment>
<svelte:fragment slot="buttons">
<Button label={ui.string.Cancel} size={'large'} disabled={processing} on:click={() => dispatch('close')} />
</svelte:fragment>
</Card>

View File

@ -84,7 +84,7 @@
{#if value}
{#if kind === 'list' || kind === 'list-header'}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="priority-container" on:click={handlePriorityEditorOpened}>
<div class="priority-container" class:cursor-pointer={isEditable} on:click={handlePriorityEditorOpened}>
<div class="icon">
{#if issuePriorities[value.priority]?.icon}<Icon icon={issuePriorities[value.priority]?.icon} {size} />{/if}
</div>
@ -116,7 +116,6 @@
align-items: center;
flex-shrink: 0;
min-width: 0;
cursor: pointer;
.icon {
display: flex;

View File

@ -21,10 +21,11 @@
export let value: WithLookup<Issue>
export let shouldUseMargin: boolean = false
export let showParent = true
export let showParent: boolean = true
export let kind: 'list' | undefined = undefined
export let onClick: (() => void) | undefined = undefined
export let disabled = false
export let disabled: boolean = false
export let maxWidth: string | undefined = undefined
</script>
{#if value}
@ -32,6 +33,7 @@
class="name overflow-label select-text"
class:with-margin={shouldUseMargin}
class:list={kind === 'list'}
style:max-width={maxWidth}
title={value.title}
>
<DocNavLink

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Ref } from '@hcengineering/core'
import { Button, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { Button, Grid, IconArrowRight, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { Component, Issue, Project } from '@hcengineering/tracker'
import { IssueToUpdate } from '../../../utils'
@ -31,13 +31,16 @@
</script>
{#if current !== undefined}
<div class="flex-row-center p-1">
<div class="side-columns aligned-text">
<Grid rowGap={0.25} topGap>
<div class="flex-between min-h-11">
<ComponentPresenter value={current} disabled />
<IconArrowRight size={'small'} fill={'var(--theme-halfcontent-color)'} />
</div>
<span class="middle-column aligned-text">-></span>
<div class="side-columns">
<div class="flex-row-center min-h-11">
<Button
size={'large'}
shape={'round-sm'}
width={'min-content'}
on:click={(event) => {
showPopup(
ComponentReplacementPopup,
@ -67,7 +70,7 @@
</span>
</Button>
</div>
</div>
</Grid>
{/if}
<style lang="scss">

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Ref, Status } from '@hcengineering/core'
import { Button, Label, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { Button, Label, Grid, IconArrowRight, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { Component, Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { statusStore } from '@hcengineering/view-resources'
@ -81,114 +81,91 @@
</script>
{#if issues[0]?.space !== targetProject._id && (Object.keys(statusToUpdate).length > 0 || missingComponents.length > 0)}
<div class="mt-4 mb-4">
<span class="caption-color">
<Label label={tracker.string.SelectReplacement} />
</span>
<div class="mt-4">
<div class="missing-items">
<span class="side-columns">
<Label label={tracker.string.MissingItem} />
</span>
<span class="middle-column" />
<span class="side-columns">
<Label label={tracker.string.Replacement} />
</span>
</div>
<div class="mt-4">
{#each Object.keys(statusToUpdate) as status}
{@const newStatus = statusToUpdate[status]}
<div class="missing-items mt-4">
<div class="side-columns aligned-text">
<StatusRefPresenter value={getStatusRef(status)} kind={'list-header'} />
</div>
<span class="middle-column aligned-text">-></span>
<div class="side-columns">
<Button
on:click={(event) => {
showPopup(
StatusReplacementPopup,
{
statuses,
original: $statusStore.get(getStatusRef(status)),
selected: getStatusRef(newStatus.ref)
},
eventToHTMLElement(event),
(value) => {
if (value) {
const createStatus = typeof value === 'object'
const s = createStatus ? value.create : value
statusToUpdate = { ...statusToUpdate, [status]: { ref: s, create: createStatus } }
}
}
)
}}
>
<span slot="content" class="flex-row-center pointer-events-none">
<StatusRefPresenter value={getStatusRef(newStatus.ref)} />
</span>
</Button>
</div>
</div>
{/each}
</div>
<div class="mt-4">
{#each missingComponents as component}
{@const componentRef = componentToUpdate[component._id]?.ref}
<div class="missing-items mt-4">
<div class="side-columns aligned-text">
<ComponentPresenter value={component} disabled />
</div>
<span class="middle-column aligned-text">-></span>
<div class="side-columns aligned-text">
<Button
on:click={(event) => {
showPopup(
ComponentReplacementPopup,
{
components: components.filter((it) => it.space === targetProject._id),
original: component,
selected: componentRef
},
eventToHTMLElement(event),
(value) => {
if (value !== undefined) {
const createComponent = typeof value === 'object'
const c = createComponent ? value.create : value
componentToUpdate = {
...componentToUpdate,
[component._id]: { ref: c, create: createComponent }
}
}
}
)
}}
>
<span slot="content" class="flex-row-center pointer-events-none">
<ComponentRefPresenter value={componentRef} />
</span>
</Button>
</div>
</div>
{/each}
</div>
</div>
<div class="caption-color mb-4">
<Label label={tracker.string.SelectReplacement} />
</div>
{/if}
<style lang="scss">
.missing-items {
display: flex;
}
.side-columns {
width: 40%;
}
.middle-column {
width: 10%;
}
.aligned-text {
display: flex;
align-items: center;
}
</style>
<Grid rowGap={0.25} columnGap={2}>
<div class="flex-row-center min-h-8 content-dark-color text-xs font-medium tracking-1px uppercase">
<Label label={tracker.string.MissingItem} />
</div>
<div class="flex-row-center min-h-8 content-dark-color text-xs font-medium tracking-1px uppercase">
<Label label={tracker.string.Replacement} />
</div>
{#each Object.keys(statusToUpdate) as status}
{@const newStatus = statusToUpdate[status]}
<div class="flex-between min-h-11">
<StatusRefPresenter value={getStatusRef(status)} kind={'list-header'} />
<IconArrowRight size={'small'} fill={'var(--theme-halfcontent-color)'} />
</div>
<div class="flex-row-center min-h-11">
<Button
size={'large'}
shape={'round-sm'}
width={'min-content'}
on:click={(event) => {
showPopup(
StatusReplacementPopup,
{
statuses,
original: $statusStore.get(getStatusRef(status)),
selected: getStatusRef(newStatus.ref)
},
eventToHTMLElement(event),
(value) => {
if (value) {
const createStatus = typeof value === 'object'
const s = createStatus ? value.create : value
statusToUpdate = { ...statusToUpdate, [status]: { ref: s, create: createStatus } }
}
}
)
}}
>
<span slot="content" class="flex-row-center pointer-events-none">
<StatusRefPresenter value={getStatusRef(newStatus.ref)} />
</span>
</Button>
</div>
{/each}
{#each missingComponents as component}
{@const componentRef = componentToUpdate[component._id]?.ref}
<div class="flex-between min-h-11">
<ComponentPresenter value={component} disabled />
<IconArrowRight size={'small'} fill={'var(--theme-halfcontent-color)'} />
</div>
<div class="flex-row-center min-h-11">
<Button
size={'large'}
shape={'round-sm'}
width={'min-content'}
on:click={(event) => {
showPopup(
ComponentReplacementPopup,
{
components: components.filter((it) => it.space === targetProject._id),
original: component,
selected: componentRef
},
eventToHTMLElement(event),
(value) => {
if (value !== undefined) {
const createComponent = typeof value === 'object'
const c = createComponent ? value.create : value
componentToUpdate = {
...componentToUpdate,
[component._id]: { ref: c, create: createComponent }
}
}
}
)
}}
>
<span slot="content" class="flex-row-center pointer-events-none">
<ComponentRefPresenter value={componentRef} />
</span>
</Button>
</div>
{/each}
</Grid>
{/if}

View File

@ -15,7 +15,7 @@
<script lang="ts">
import { Ref } from '@hcengineering/core'
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { Button, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { Button, Grid, IconArrowRight, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { IssueToUpdate } from '../../../utils'
import StatusRefPresenter from '../StatusRefPresenter.svelte'
@ -31,13 +31,16 @@
$: original = $statusStore.get(issue.status)
</script>
<div class="flex-row-center p-1">
<div class="side-columns aligned-text">
<Grid rowGap={0.25} topGap>
<div class="flex-between min-h-11">
<StatusRefPresenter value={issue.status} size={'small'} />
<IconArrowRight size={'small'} fill={'var(--theme-halfcontent-color)'} />
</div>
<span class="middle-column aligned-text">-></span>
<div class="side-columns">
<div class="flex-row-center min-h-11">
<Button
size={'large'}
shape={'round-sm'}
width={'min-content'}
on:click={(event) => {
showPopup(
StatusReplacementPopup,
@ -63,17 +66,4 @@
</span>
</Button>
</div>
</div>
<style lang="scss">
.side-columns {
width: 45%;
}
.middle-column {
width: 10%;
}
.aligned-text {
display: flex;
align-items: center;
}
</style>
</Grid>

View File

@ -121,7 +121,7 @@
on:dragend={resetDrag}
>
<div class="draggable-container">
<div class="draggable-mark"><IconCircles /></div>
<IconCircles size={'small'} />
</div>
<div class="flex-row-center ml-6 clear-mins gap-2">
<StatusEditor
@ -209,23 +209,18 @@
position: absolute;
display: flex;
align-items: center;
height: 100%;
width: 1.5rem;
top: 50%;
left: 0.125rem;
width: 1rem;
height: 1rem;
opacity: 0;
transform: translateY(-50%);
transition: opacity 0.1s;
cursor: grabbing;
.draggable-mark {
opacity: 0;
width: 0.375rem;
height: 1rem;
margin-left: 0.75rem;
transition: opacity 0.1s;
}
}
&:hover {
.draggable-mark {
opacity: 0.4;
}
&:hover .draggable-container {
opacity: 0.4;
}
&.is-dragging::before {

View File

@ -123,7 +123,7 @@
on:dragend={resetDrag}
>
<div class="draggable-container">
<div class="draggable-mark"><IconCircles /></div>
<IconCircles size={'small'} />
</div>
<div class="flex-row-center ml-6 clear-mins gap-2">
<PriorityEditor
@ -204,23 +204,18 @@
position: absolute;
display: flex;
align-items: center;
height: 100%;
width: 1.5rem;
top: 50%;
left: 0.125rem;
width: 1rem;
height: 1rem;
opacity: 0;
transform: translateY(-50%);
transition: opacity 0.1s;
cursor: grabbing;
.draggable-mark {
opacity: 0;
width: 0.375rem;
height: 1rem;
margin-left: 0.75rem;
transition: opacity 0.1s;
}
}
&:hover {
.draggable-mark {
opacity: 0.4;
}
&:hover .draggable-container {
opacity: 0.4;
}
&.is-dragging::before {

View File

@ -51,13 +51,13 @@
$: canSave = !isSaving && (value.name ?? '').length > 0
</script>
<div class="flex-between background-button-bg-color border-radius-1 p-2 root">
<div class="flex-between border-radius-1 statusEditor-container">
<div class="flex flex-grow items-center clear-mins inputs">
<div class="flex-no-shrink draggable-mark">
{#if !isSingle}<IconCircles />{/if}
{#if !isSingle}<IconCircles size={'small'} />{/if}
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="flex-no-shrink ml-2 color" on:click={pickColor}>
<div class="flex-no-shrink color" on:click={pickColor}>
<div class="dot" style="background-color: {getPlatformColorDef(value.color ?? 0, $themeStore.dark).color}" />
</div>
<div class="ml-2 w-full name">
@ -67,61 +67,74 @@
<StatusInput bind:value={value.description} placeholder={tracker.string.Description} fill />
</div>
</div>
<div class="buttons-group small-gap flex-no-shrink ml-2 mr-1">
<Button label={presentation.string.Cancel} kind="regular" on:click={() => dispatch('cancel')} />
<Button label={presentation.string.Save} kind="accented" disabled={!canSave} on:click={() => dispatch('save')} />
<div class="buttons-group small-gap flex-no-shrink ml-2">
<Button label={presentation.string.Cancel} size={'small'} kind={'regular'} on:click={() => dispatch('cancel')} />
<Button
label={presentation.string.Save}
size={'small'}
kind={'accented'}
disabled={!canSave}
on:click={() => dispatch('save')}
/>
</div>
</div>
<style lang="scss">
.root {
.statusEditor-container {
padding: 0.75rem 1.125rem;
line-height: 1.125rem;
background-color: var(--theme-button-default);
.draggable-mark {
position: absolute;
top: 50%;
left: 0.125rem;
width: 1rem;
height: 1rem;
transform: translateY(-50%);
opacity: 0;
&.draggable {
transition: opacity 0.15s;
&::before {
position: absolute;
content: '';
inset: -0.5rem;
}
}
}
.color {
position: relative;
width: 1.75rem;
height: 1.75rem;
background-color: var(--theme-bg-dark-color);
border: 1px solid transparent;
border-radius: 0.25rem;
cursor: pointer;
.dot {
position: absolute;
content: '';
border-radius: 50%;
inset: 30%;
}
}
.inputs {
overflow: hidden;
white-space: nowrap;
.name {
max-width: 10rem;
}
}
&:hover {
background-color: var(--theme-button-hovered);
.draggable-mark {
opacity: 0.4;
}
}
}
.inputs {
overflow: hidden;
white-space: nowrap;
.name {
max-width: 10rem;
}
}
.draggable-mark {
position: relative;
opacity: 0;
width: 0.375rem;
height: 1rem;
margin-left: 0.25rem;
transition: opacity 0.1s;
&::before {
position: absolute;
content: '';
inset: -0.5rem;
}
}
.color {
position: relative;
width: 1.75rem;
height: 1.75rem;
background-color: var(--accent-bg-color);
border: 1px solid transparent;
border-radius: 0.25rem;
cursor: pointer;
.dot {
position: absolute;
content: '';
border-radius: 50%;
inset: 30%;
}
}
</style>

View File

@ -57,8 +57,6 @@
<style lang="scss">
.root {
font-weight: 500;
&.fill {
width: 100%;
@ -69,7 +67,7 @@
input {
padding: 0.25rem 0.5rem;
background-color: var(--accent-bg-color);
background-color: var(--theme-bg-dark-color);
border: 1px solid transparent;
border-radius: 0.25rem;

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { IssueStatus } from '@hcengineering/tracker'
import { Icon, IconCircles, IconClose, IconEdit, Label, tooltip } from '@hcengineering/ui'
import { Icon, IconCircles, IconDelete, IconEdit, Label, tooltip } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import IssueStatusIcon from '../issues/IssueStatusIcon.svelte'
@ -30,12 +30,12 @@
}
</script>
<div class="flex-between background-button-bg-color border-radius-1 p-2 root" on:dblclick|preventDefault={edit}>
<div class="flex flex-grow items-center">
<div class="flex-no-shrink draggable-mark" class:draggable={!isSingle}>
<IconCircles />
<div class="flex-between border-radius-1 px-6 h-8 status-container" on:dblclick|preventDefault={edit}>
<div class="flex-row-center flex-grow">
<div class="draggable-mark" class:draggable={!isSingle}>
<IconCircles size={'small'} />
</div>
<div class="flex-no-shrink ml-2">
<div class="flex-no-shrink">
<IssueStatusIcon {value} size="small" />
</div>
<span class="caption-color ml-2">{value.name}</span>
@ -43,7 +43,7 @@
<span>&nbsp;·&nbsp;{value.description}</span>
{/if}
</div>
<div class="buttons-group flex-no-shrink mr-1">
<div class="buttons-group flex-no-shrink">
{#if isDefault}
<Label label={tracker.string.Default} />
{:else if value.category === tracker.issueStatusCategory.Backlog || value.category === tracker.issueStatusCategory.Unstarted}
@ -58,7 +58,7 @@
use:tooltip={{ label: tracker.string.EditWorkflowStatus, direction: 'bottom' }}
on:click|preventDefault={edit}
>
<Icon icon={IconEdit} size="small" />
<Icon icon={IconEdit} size={'small'} />
</div>
{#if !isDefault}
<!-- svelte-ignore a11y-click-events-have-key-events -->
@ -67,54 +67,45 @@
use:tooltip={{ label: tracker.string.DeleteWorkflowStatus, direction: 'bottom' }}
on:click|preventDefault={() => dispatch('delete', value)}
>
<Icon icon={IconClose} size="small" />
<Icon icon={IconDelete} size={'small'} />
</div>
{/if}
</div>
</div>
<style lang="scss">
.root {
&:hover {
.btn {
opacity: 1;
}
.status-container {
background-color: var(--theme-button-default);
.draggable-mark.draggable {
cursor: grab;
opacity: 0.4;
}
}
}
.btn {
position: relative;
opacity: 0;
cursor: pointer;
color: var(--content-color);
transition: color 0.15s;
transition: opacity 0.15s;
&:hover {
color: var(--caption-color);
}
&::before {
.draggable-mark {
position: absolute;
content: '';
inset: -0.5rem;
top: 0.5rem;
left: 0.125rem;
width: 1rem;
height: 1rem;
opacity: 0;
&.draggable {
transition: opacity 0.15s;
&::before {
position: absolute;
content: '';
inset: -0.5rem;
}
}
}
}
.draggable-mark {
opacity: 0;
position: relative;
width: 0.375rem;
height: 1rem;
margin-left: 0.25rem;
&.draggable {
.btn {
position: relative;
color: var(--theme-dark-color);
transition: color 0.15s;
transition: opacity 0.15s;
opacity: 0;
cursor: pointer;
&:hover {
color: var(--theme-content-color);
}
&::before {
position: absolute;
@ -122,5 +113,22 @@
inset: -0.5rem;
}
}
&:hover,
&:active {
.btn {
opacity: 1;
}
.draggable-mark.draggable {
opacity: 0.4;
cursor: grab;
}
}
&:hover {
background-color: var(--theme-button-hovered);
}
&:active {
background-color: var(--theme-button-pressed);
}
}
</style>

View File

@ -14,20 +14,9 @@
-->
<script lang="ts">
import core, { Class, Data, Ref, SortingOrder, StatusCategory } from '@hcengineering/core'
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
import { createQuery, getClient, MessageBox, Card } from '@hcengineering/presentation'
import { calcRank, IssueStatus, Project } from '@hcengineering/tracker'
import {
Button,
closeTooltip,
ExpandCollapse,
Icon,
IconAdd,
Label,
Loading,
Panel,
Scroller,
showPopup
} from '@hcengineering/ui'
import { Button, closeTooltip, ExpandCollapse, IconAdd, Label, Loading, Scroller, showPopup } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { flip } from 'svelte/animate'
import tracker from '../../plugin'
@ -241,28 +230,25 @@
$: projectStatuses = $statusStore.getDocs().filter((status) => status.space === projectId)
</script>
<Panel isHeader={false} isAside={false} on:fullsize on:close={() => dispatch('close')}>
<svelte:fragment slot="title">
<div class="antiTitle icon-wrapper">
<div class="wrapped-icon">
<Icon icon={tracker.icon.Issue} size="small" />
</div>
<div class="title-wrapper">
<span class="wrapped-title">
<Label label={tracker.string.ManageWorkflowStatuses} />
</span>
{#if project}
<span class="wrapped-subtitle">{project.name}</span>
{/if}
</div>
</div>
<Card
label={tracker.string.ManageWorkflowStatuses}
onCancel={() => dispatch('close')}
okAction={() => {}}
accentHeader
hideAttachments
hideFooter
>
<svelte:fragment slot="subheader">
{#if project}
<span class="content-halfcontent-color overflow-label" style:margin-top={'-.5rem'}>{project.name}</span>
{/if}
</svelte:fragment>
{#if project === undefined || statusCategories === undefined || projectStatuses.length === 0}
<Loading />
{:else}
<Scroller>
<div class="popupPanel-body__main-content py-10 clear-mins flex-no-shrink">
<div class="content">
{#each statusCategories as category}
{@const statuses = projectStatuses.filter((s) => s.category === category._id) ?? []}
{@const isSingle = statuses.length === 1}
@ -270,10 +256,9 @@
<Label label={category.label} />
<Button
showTooltip={{ label: tracker.string.AddWorkflowStatus }}
width="min-content"
icon={IconAdd}
size="small"
kind="ghost"
size={'medium'}
kind={'ghost'}
on:click={() => {
closeTooltip()
editingStatus = { category: category._id, color: category.color }
@ -320,43 +305,60 @@
{/if}
</div>
{/each}
<ExpandCollapse isExpanded>
{#if editingStatus && !('_id' in editingStatus) && editingStatus.category === category._id}
{#if editingStatus && !('_id' in editingStatus) && editingStatus.category === category._id}
<ExpandCollapse isExpanded>
<StatusEditor value={editingStatus} on:cancel={cancelEditing} on:save={addStatus} {isSaving} isSingle />
{/if}
</ExpandCollapse>
</ExpandCollapse>
{/if}
</div>
{/each}
</div>
</Scroller>
{/if}
</Panel>
</Card>
<style lang="scss">
.row {
position: relative;
margin-bottom: 0.25rem;
.content {
display: flex;
flex-direction: column;
flex-shrink: 0;
margin-left: auto;
margin-right: auto;
width: 100%;
max-width: 54rem;
min-width: 0;
min-height: 0;
&.is-dragged-over-up::before {
position: absolute;
content: '';
inset: 0;
border-top: 1px solid var(--theme-caret-color);
.category-name {
margin: 1rem 0 0.25rem 0;
text-transform: uppercase;
font-weight: 500;
font-size: 0.625rem;
letter-spacing: 1px;
color: var(--theme-dark-color);
&:first-child {
margin: 0 0 0.25rem 0;
}
}
.row {
position: relative;
&.is-dragged-over-down::before {
position: absolute;
content: '';
inset: 0;
border-bottom: 1px solid var(--theme-caret-color);
&.is-dragged-over-up::before {
position: absolute;
content: '';
inset: 0;
border-top: 1px solid var(--theme-caret-color);
}
&.is-dragged-over-down::before {
position: absolute;
content: '';
inset: 0;
border-bottom: 1px solid var(--theme-caret-color);
}
}
}
.category-name {
margin: 1rem 0 0.5rem 0;
&:first-child {
margin: 0 0 0.5rem 0;
.row + .row {
margin-top: 0.25rem;
}
}
</style>

View File

@ -202,12 +202,12 @@ export async function queryIssue<D extends Issue> (
}
async function move (issues: Issue | Issue[]): Promise<void> {
showPopup(MoveIssues, { selected: issues })
showPopup(MoveIssues, { selected: issues }, 'top')
}
async function editWorkflowStatuses (project: Project | undefined): Promise<void> {
if (project !== undefined) {
showPopup(Statuses, { projectId: project._id, projectClass: project._class }, 'float')
showPopup(Statuses, { projectId: project._id, projectClass: project._class }, 'top')
}
}

View File

@ -180,6 +180,7 @@ export default mergeIds(trackerId, tracker, {
Duplicate: '' as IntlString,
MoveIssues: '' as IntlString,
MoveIssuesDescription: '' as IntlString,
ManageAttributes: '' as IntlString,
KeepOriginalAttributes: '' as IntlString,
KeepOriginalAttributesTooltip: '' as IntlString,
SelectReplacement: '' as IntlString,

View File

@ -22,7 +22,7 @@
export let object: Doc | undefined
export let disabled = false
export let onClick: ((event: MouseEvent) => void) | undefined = undefined
export let noUnderline = false
export let noUnderline = disabled
export let inline = false
export let colorInherit: boolean = false
export let component: AnyComponent = view.component.EditDoc

View File

@ -30,18 +30,18 @@
position: relative;
display: flex;
flex-direction: column;
padding: 2rem 1.75rem 1.75rem;
border-radius: 0.75rem;
background: var(--popup-bg-color);
box-shadow: var(--popup-shadow);
padding: 1rem 1.5rem 1.5rem;
border-radius: 0.5rem;
background-color: var(--theme-popup-color);
border: 1px solid var(--theme-popup-divider);
box-shadow: var(--theme-popup-shadow);
.label {
flex-shrink: 0;
margin-bottom: 1.25rem;
text-transform: uppercase;
font-weight: 600;
font-size: 0.75rem;
color: var(--dark-color);
margin-bottom: 1rem;
min-height: 1.75rem;
font-size: 1rem;
color: var(--theme-caption-color);
}
.content {

View File

@ -26,7 +26,7 @@
<span
use:tooltip={{ label: plugin.string.ShowPreviewOnClick }}
on:click={() => {
showPopup(IndexedDocumentPreview, { objectId: value._id, search })
showPopup(IndexedDocumentPreview, { objectId: value._id, search }, 'centered')
}}
>
{#if value.$source?.$score}

View File

@ -91,9 +91,7 @@
>
<div class="draggable-container">
<div class="draggable-mark">
<IconCircles />
<div class="space" />
<IconCircles />
<IconCircles size={'small'} />
</div>
</div>
<div class="flex-center relative mr-1" use:tooltip={{ label: view.string.Select, direction: 'bottom' }}>
@ -168,16 +166,12 @@
{/each}
{#if compactMode}
<div class="panel-trigger" tabindex="-1">
<IconCircles />
<div class="space" />
<IconCircles />
<IconCircles size={'small'} />
</div>
<div class="hidden-panel" tabindex="-1">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="header" on:click={(ev) => ev.currentTarget.blur()}>
<IconCircles />
<div class="space" />
<IconCircles />
<IconCircles size={'small'} />
</div>
<div class="scroll-box gap-2">
{#if mobile}

View File

@ -33,10 +33,12 @@
<div
class="root flex background-button-bg-color border-radius-1"
{style}
style:background={'red'}
on:dblclick|preventDefault={isEditable && !isEditing ? () => dispatch('edit') : undefined}
>
<div class="flex-center ml-2">
<div class="flex-no-shrink circles-mark" class:isDraggable><IconCircles /></div>
<div class="flex-no-shrink circles-mark" class:isDraggable><IconCircles size={'small'} /></div>
!!!
</div>
<div class="root flex flex-between items-center w-full p-2">

View File

@ -238,7 +238,7 @@ test('report-time-from-main-view', async ({ page }) => {
await page.waitForSelector('text="Estimation"')
await page.click('button:has-text("Add time report")')
await page.waitForSelector('.antiCard-header >> .antiCard-header__title-wrap >> span:has-text("Add time report")')
await page.waitForSelector('[id="tracker\\:string\\:TimeSpendReportAdd"] >> text=Add time report')
await expect(page.locator('button:has-text("Create")')).toBeDisabled()
await page.fill('[placeholder="Reported\\ days"]', `${time}`)
await expect(page.locator('button:has-text("Create")')).toBeEnabled()

File diff suppressed because one or more lines are too long