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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -40,8 +40,10 @@
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
&.withSub { padding: 1.5rem 1.5rem 0; } &.withSub:not(.thinHeader) { padding: 1.5rem 1.5rem 0; }
&:not(.withSub) { padding: 1.5rem 1.5rem 1rem; } &.withSub.thinHeader { padding: 1rem 1.5rem 0; }
&.thinHeader:not(.withSub) { padding: 1rem 1.5rem; }
&:not(.withSub, .thinHeader) { padding: 1.5rem; }
&__title-wrap { &__title-wrap {
display: flex; display: flex;
@ -81,7 +83,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
padding: .5rem 1.5rem 1rem; padding: .5rem 1.5rem 1.5rem;
min-width: 0; min-width: 0;
min-height: 0; min-height: 0;
} }
@ -124,11 +126,24 @@
order: 1; order: 1;
} }
} }
.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 { &__container {
display: flex; display: flex;
align-items: center; align-items: center;
@ -138,6 +153,8 @@
& > *:last-child { margin-right: 1.5rem; } & > *:last-child { margin-right: 1.5rem; }
} }
} }
.antiCard-block { padding: 1.5rem; }
.antiCard-blocks { padding: .75rem 1.5rem; }
.antiCard-footer { .antiCard-footer {
overflow: hidden; overflow: hidden;

View File

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

View File

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

View File

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

View File

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

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"> <script lang="ts">
const fill: string = 'var(--caption-color)' export let size: 'small' | 'medium' | 'large'
const fill: string = 'var(--theme-caption-color)'
</script> </script>
<svg {fill} viewBox="0 0 6 16" xmlns="http://www.w3.org/2000/svg"> <svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<circle cx="1" cy="1" r="1" /> <path
<circle cx="4.5" cy="1" r="1" /> 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"
<circle cx="1" cy="4.5" r="1" /> />
<circle cx="4.5" cy="4.5" r="1" /> <path
<circle cx="1" cy="8" r="1" /> 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"
<circle cx="4.5" cy="8" r="1" /> />
<circle cx="1" cy="11.5" r="1" /> <path
<circle cx="4.5" cy="11.5" r="1" /> 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"
<circle cx="1" cy="15" r="1" /> />
<circle cx="4.5" cy="15" r="1" /> <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> </svg>

View File

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

View File

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

View File

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

View File

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

View File

@ -57,7 +57,7 @@
<Scroller> <Scroller>
{#each filtered as item, i} {#each filtered as item, i}
<div <div
class="flex-between flex-nowrap item mb-2" class="flex-between flex-nowrap item step-tb25"
draggable={true} draggable={true}
bind:this={elements[i]} bind:this={elements[i]}
on:dragover|preventDefault={(ev) => { on:dragover|preventDefault={(ev) => {
@ -71,9 +71,9 @@
selected = undefined selected = undefined
}} }}
> >
<div class="flex"> <div class="flex-row-center">
<div class="circles-mark"><IconCircles /></div> <div class="circles-mark"><IconCircles size={'small'} /></div>
<span class="overflow-label">{item}</span> <span class="overflow-label mx-2">{item}</span>
</div> </div>
<ActionIcon <ActionIcon
icon={IconDelete} icon={IconDelete}
@ -85,6 +85,7 @@
/> />
</div> </div>
{/each} {/each}
{#if filtered.length}<div class="antiVSpacer x4" />{/if}
</Scroller> </Scroller>
{#if filtered.length === 0} {#if filtered.length === 0}
<Label label={presentation.string.NoMatchesFound} /> <Label label={presentation.string.NoMatchesFound} />
@ -92,26 +93,35 @@
<style lang="scss"> <style lang="scss">
.item { .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 { &:hover {
background-color: var(--theme-button-hovered);
.circles-mark { .circles-mark {
cursor: grab; cursor: grab;
opacity: 1; opacity: 1;
} }
} }
} &:active {
.circles-mark { background-color: var(--theme-button-pressed);
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;
} }
} }
</style> </style>

View File

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

View File

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

View File

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

View File

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

View File

@ -17,12 +17,11 @@
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { Ref, Status } from '@hcengineering/core' 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 { 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 view from '@hcengineering/view'
import { statusStore } from '@hcengineering/view-resources' import { statusStore } from '@hcengineering/view-resources'
import { getEmbeddedLabel } from '@hcengineering/platform'
import tracker from '../../plugin' import tracker from '../../plugin'
import { import {
@ -265,198 +264,140 @@
} }
</script> </script>
<div class="container"> <Card
{#if !showManageAttributes} label={showManageAttributes ? tracker.string.ManageAttributes : tracker.string.MoveIssues}
<div class="space-between"> okLabel={view.string.Move}
<span class="fs-title aligned-text"> okAction={moveAll}
<Label label={tracker.string.MoveIssues} /> 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> </span>
<Button icon={IconClose} iconProps={{ size: 'medium' }} kind="ghost" on:click={() => dispatch('close')} /> {/if}
</div> </svelte:fragment>
<div> {#if !showManageAttributes}
<Label label={tracker.string.MoveIssuesDescription} /> <div class="flex-between">
</div>
<div class="space-between mt-6 mb-4">
{#if currentSpace !== undefined} {#if currentSpace !== undefined}
<SpaceSelector <SpaceSelector
_class={currentSpace._class} _class={currentSpace._class}
label={hierarchy.getClass(tracker.class.Project).label} label={hierarchy.getClass(tracker.class.Project).label}
bind:space bind:space
kind={'regular'} kind={'regular'}
size={'small'} size={'large'}
component={ProjectPresenter} component={ProjectPresenter}
iconWithEmoji={tracker.component.IconWithEmoji} iconWithEmoji={tracker.component.IconWithEmoji}
defaultIcon={tracker.icon.Home} defaultIcon={tracker.icon.Home}
/> />
<!-- svelte-ignore a11y-click-events-have-key-events --> <Button
<span label={tracker.string.ManageAttributes}
class="aligned-text" iconRight={IconForward}
class:disabled={!isManageAttributesAvailable} kind={'ghost'}
on:click|stopPropagation={() => { size={'small'}
padding={'0 1rem'}
disabled={!isManageAttributesAvailable}
on:click={() => {
if (!isManageAttributesAvailable) { if (!isManageAttributesAvailable) {
return return
} }
showManageAttributes = !showManageAttributes showManageAttributes = !showManageAttributes
}} }}
> />
Manage attributes >
</span>
{/if} {/if}
</div> </div>
{:else if loading}<Spinner />{/if}
<div class="divider" /> <svelte:fragment slot="blocks" let:block>
{#if currentSpace !== undefined && !keepOriginalAttribytes} {#if !showManageAttributes}
<SelectReplacement {#if currentSpace !== undefined && !keepOriginalAttribytes}
{statuses} <SelectReplacement
{components} {statuses}
targetProject={currentSpace} {components}
issues={toMove} targetProject={currentSpace}
bind:statusToUpdate issues={toMove}
bind:componentToUpdate bind:statusToUpdate
/> bind:componentToUpdate
<div class="divider" /> />
{/if} {/if}
{:else} {:else if toMove.length > 0 && currentSpace}
<div class="space-between pb-4"> {@const issue = toMove[block]}
<!-- svelte-ignore a11y-click-events-have-key-events --> {@const upd = issueToUpdate.get(issue._id) ?? {}}
<span {@const originalComponent = components.find((it) => it._id === issue.component)}
class="fs-title aligned-text" {@const targetComponent = components.find(
on:click|stopPropagation={() => (showManageAttributes = !showManageAttributes)} (it) => it.space === currentSpace?._id && it.label === originalComponent?.label
> )}
<Label label={getEmbeddedLabel('< Manage attributes')} /> {#key keepOriginalAttribytes}
</span> {#if issue.space !== currentSpace._id && (upd.status !== undefined || upd.component !== undefined)}
<Button icon={IconClose} iconProps={{ size: 'medium' }} kind="ghost" on:click={() => dispatch('close')} /> <div class="flex-row-center min-h-9 gap-1-5 content-color">
</div> <PriorityEditor value={issue} isEditable={false} kind={'list'} size={'small'} shouldShowLabel={false} />
<div class="divider" /> <IssuePresenter value={issue} disabled kind={'list'} />
<TitlePresenter disabled value={issue} showParent={false} maxWidth={'7.5rem'} />
<div class="issues-move flex-col"> </div>
{#if loading} {#key upd.status}
<Spinner /> <StatusMovePresenter {issue} bind:issueToUpdate targetProject={currentSpace} {statuses} />
{: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}
{/key} {/key}
{/each} {#if targetComponent === undefined}
{/if} {#key upd.component}
</div> <ComponentMovePresenter {issue} bind:issueToUpdate targetProject={currentSpace} {components} />
{/if} {/key}
{/if}
{/if}
{/key}
{/if}
</svelte:fragment>
<div class="space-between mt-4"> <svelte:fragment slot="footer">
<div <div
class="aligned-text" class="flex-row-center gap-2"
use:tooltip={{ use:tooltip={{
component: Label, component: Label,
props: { label: tracker.string.KeepOriginalAttributesTooltip } props: { label: tracker.string.KeepOriginalAttributesTooltip }
}} }}
> >
<div class="mr-2"> <Toggle
<Toggle disabled={!isManageAttributesAvailable}
disabled={!isManageAttributesAvailable} on:change={() => {
on:change={() => { keepOriginalAttribytes = !keepOriginalAttribytes
keepOriginalAttribytes = !keepOriginalAttribytes if (!keepOriginalAttribytes) {
if (!keepOriginalAttribytes) { statusToUpdate = {}
statusToUpdate = {} componentToUpdate = {}
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')
}} }}
disabled={processing}
/> />
<span class="lines-limit-2">
<Label label={tracker.string.KeepOriginalAttributes} />
</span>
</div> </div>
</div> </svelte:fragment>
</div> <svelte:fragment slot="buttons">
<Button label={ui.string.Cancel} size={'large'} disabled={processing} on:click={() => dispatch('close')} />
<style lang="scss"> </svelte:fragment>
.container { </Card>
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>

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Ref, Status } from '@hcengineering/core' 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 { Component, Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { statusStore } from '@hcengineering/view-resources' import { statusStore } from '@hcengineering/view-resources'
@ -81,114 +81,91 @@
</script> </script>
{#if issues[0]?.space !== targetProject._id && (Object.keys(statusToUpdate).length > 0 || missingComponents.length > 0)} {#if issues[0]?.space !== targetProject._id && (Object.keys(statusToUpdate).length > 0 || missingComponents.length > 0)}
<div class="mt-4 mb-4"> <div class="caption-color mb-4">
<span class="caption-color"> <Label label={tracker.string.SelectReplacement} />
<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> </div>
{/if}
<style lang="scss"> <Grid rowGap={0.25} columnGap={2}>
.missing-items { <div class="flex-row-center min-h-8 content-dark-color text-xs font-medium tracking-1px uppercase">
display: flex; <Label label={tracker.string.MissingItem} />
} </div>
.side-columns { <div class="flex-row-center min-h-8 content-dark-color text-xs font-medium tracking-1px uppercase">
width: 40%; <Label label={tracker.string.Replacement} />
} </div>
.middle-column { {#each Object.keys(statusToUpdate) as status}
width: 10%; {@const newStatus = statusToUpdate[status]}
} <div class="flex-between min-h-11">
.aligned-text { <StatusRefPresenter value={getStatusRef(status)} kind={'list-header'} />
display: flex; <IconArrowRight size={'small'} fill={'var(--theme-halfcontent-color)'} />
align-items: center; </div>
} <div class="flex-row-center min-h-11">
</style> <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"> <script lang="ts">
import { Ref } from '@hcengineering/core' import { Ref } from '@hcengineering/core'
import { Issue, IssueStatus, Project } from '@hcengineering/tracker' 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 { IssueToUpdate } from '../../../utils'
import StatusRefPresenter from '../StatusRefPresenter.svelte' import StatusRefPresenter from '../StatusRefPresenter.svelte'
@ -31,13 +31,16 @@
$: original = $statusStore.get(issue.status) $: original = $statusStore.get(issue.status)
</script> </script>
<div class="flex-row-center p-1"> <Grid rowGap={0.25} topGap>
<div class="side-columns aligned-text"> <div class="flex-between min-h-11">
<StatusRefPresenter value={issue.status} size={'small'} /> <StatusRefPresenter value={issue.status} size={'small'} />
<IconArrowRight size={'small'} fill={'var(--theme-halfcontent-color)'} />
</div> </div>
<span class="middle-column aligned-text">-></span> <div class="flex-row-center min-h-11">
<div class="side-columns">
<Button <Button
size={'large'}
shape={'round-sm'}
width={'min-content'}
on:click={(event) => { on:click={(event) => {
showPopup( showPopup(
StatusReplacementPopup, StatusReplacementPopup,
@ -63,17 +66,4 @@
</span> </span>
</Button> </Button>
</div> </div>
</div> </Grid>
<style lang="scss">
.side-columns {
width: 45%;
}
.middle-column {
width: 10%;
}
.aligned-text {
display: flex;
align-items: center;
}
</style>

View File

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

View File

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

View File

@ -51,13 +51,13 @@
$: canSave = !isSaving && (value.name ?? '').length > 0 $: canSave = !isSaving && (value.name ?? '').length > 0
</script> </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 flex-grow items-center clear-mins inputs">
<div class="flex-no-shrink draggable-mark"> <div class="flex-no-shrink draggable-mark">
{#if !isSingle}<IconCircles />{/if} {#if !isSingle}<IconCircles size={'small'} />{/if}
</div> </div>
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- 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 class="dot" style="background-color: {getPlatformColorDef(value.color ?? 0, $themeStore.dark).color}" />
</div> </div>
<div class="ml-2 w-full name"> <div class="ml-2 w-full name">
@ -67,61 +67,74 @@
<StatusInput bind:value={value.description} placeholder={tracker.string.Description} fill /> <StatusInput bind:value={value.description} placeholder={tracker.string.Description} fill />
</div> </div>
</div> </div>
<div class="buttons-group small-gap flex-no-shrink ml-2 mr-1"> <div class="buttons-group small-gap flex-no-shrink ml-2">
<Button label={presentation.string.Cancel} kind="regular" on:click={() => dispatch('cancel')} /> <Button label={presentation.string.Cancel} size={'small'} kind={'regular'} on:click={() => dispatch('cancel')} />
<Button label={presentation.string.Save} kind="accented" disabled={!canSave} on:click={() => dispatch('save')} /> <Button
label={presentation.string.Save}
size={'small'}
kind={'accented'}
disabled={!canSave}
on:click={() => dispatch('save')}
/>
</div> </div>
</div> </div>
<style lang="scss"> <style lang="scss">
.root { .statusEditor-container {
padding: 0.75rem 1.125rem;
line-height: 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 { &:hover {
background-color: var(--theme-button-hovered);
.draggable-mark { .draggable-mark {
opacity: 0.4; 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> </style>

View File

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

View File

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

View File

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

View File

@ -202,12 +202,12 @@ export async function queryIssue<D extends Issue> (
} }
async function move (issues: Issue | Issue[]): Promise<void> { 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> { async function editWorkflowStatuses (project: Project | undefined): Promise<void> {
if (project !== undefined) { 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, Duplicate: '' as IntlString,
MoveIssues: '' as IntlString, MoveIssues: '' as IntlString,
MoveIssuesDescription: '' as IntlString, MoveIssuesDescription: '' as IntlString,
ManageAttributes: '' as IntlString,
KeepOriginalAttributes: '' as IntlString, KeepOriginalAttributes: '' as IntlString,
KeepOriginalAttributesTooltip: '' as IntlString, KeepOriginalAttributesTooltip: '' as IntlString,
SelectReplacement: '' as IntlString, SelectReplacement: '' as IntlString,

View File

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

View File

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

View File

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

View File

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

View File

@ -33,10 +33,12 @@
<div <div
class="root flex background-button-bg-color border-radius-1" class="root flex background-button-bg-color border-radius-1"
{style} {style}
style:background={'red'}
on:dblclick|preventDefault={isEditable && !isEditing ? () => dispatch('edit') : undefined} on:dblclick|preventDefault={isEditable && !isEditing ? () => dispatch('edit') : undefined}
> >
<div class="flex-center ml-2"> <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>
<div class="root flex flex-between items-center w-full p-2"> <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.waitForSelector('text="Estimation"')
await page.click('button:has-text("Add time report")') 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 expect(page.locator('button:has-text("Create")')).toBeDisabled()
await page.fill('[placeholder="Reported\\ days"]', `${time}`) await page.fill('[placeholder="Reported\\ days"]', `${time}`)
await expect(page.locator('button:has-text("Create")')).toBeEnabled() await expect(page.locator('button:has-text("Create")')).toBeEnabled()

File diff suppressed because one or more lines are too long