Update EditStatuses, PDFViewer. Add full size for Panel. (#1797)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-05-19 09:42:38 +03:00 committed by GitHub
parent 8abadca450
commit d277134032
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 446 additions and 270 deletions

View File

@ -32,9 +32,18 @@
export let isSub: boolean = true export let isSub: boolean = true
export let isAside: boolean = true export let isAside: boolean = true
export let isCustomAttr: boolean = true export let isCustomAttr: boolean = true
export let isFullSize: boolean = false
</script> </script>
<Panel bind:isAside isHeader={$$slots.header || isHeader} bind:panelWidth bind:innerWidth on:close> <Panel
bind:isAside
bind:isFullSize
isHeader={$$slots.header || isHeader}
bind:panelWidth
bind:innerWidth
on:close
on:fullsize
>
<svelte:fragment slot="title"> <svelte:fragment slot="title">
<div class="popupPanel-title__content-container antiTitle"> <div class="popupPanel-title__content-container antiTitle">
{#if $$slots.navigator} {#if $$slots.navigator}

View File

@ -4,7 +4,7 @@
"Cancel": "Отменить", "Cancel": "Отменить",
"Ok": "Ок", "Ok": "Ок",
"Save": "Сохранить", "Save": "Сохранить",
"Download": "Загрузить", "Download": "Скачать",
"Close": "Закрыть", "Close": "Закрыть",
"NotSelected": "Не выбрано", "NotSelected": "Не выбрано",
"Deselect": "Снять выделение", "Deselect": "Снять выделение",

View File

@ -13,43 +13,88 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { Button, CircleButton, IconClose, ActionIcon } from '@anticrm/ui' import { Button, Tooltip, Panel } from '@anticrm/ui'
import type { PopupOptions } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import presentation from '..' import presentation from '..'
import { getFileUrl } from '../utils' import { getFileUrl } from '../utils'
import Avatar from './Avatar.svelte'
import MaximizeH from './icons/MaximizeH.svelte' import MaximizeH from './icons/MaximizeH.svelte'
import MaximizeV from './icons/MaximizeV.svelte' import MaximizeV from './icons/MaximizeV.svelte'
import MaximizeO from './icons/MaximizeO.svelte' import MaximizeO from './icons/MaximizeO.svelte'
import Download from './icons/Download.svelte'
export let file: string export let file: string
export let name: string export let name: string
export let contentType: string | undefined export let contentType: string | undefined
export let options: PopupOptions
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let imgView: 'img-horizontal-fit' | 'img-vertical-fit' | 'img-original-fit' = 'img-horizontal-fit' let imgView: 'img-horizontal-fit' | 'img-vertical-fit' | 'img-original-fit' = 'img-horizontal-fit'
function iconLabel (name: string): string {
const parts = name.split('.')
const ext = parts[parts.length - 1]
return ext.substring(0, 4).toUpperCase()
}
</script> </script>
<div <Panel
class="antiOverlay" isHeader={false}
on:click={() => { isAside={options && options.fullSize}
isFullSize
on:fullsize
on:close={() => {
dispatch('close') dispatch('close')
}} }}
/> >
<div class="antiDialogs antiComponent pdfviewer-container"> <svelte:fragment slot="title">
<div class="ac-header short mirror"> <div class="antiTitle icon-wrapper">
<div class="ac-header__wrap-title"> <div class="wrapped-icon">
<div class="ac-header__icon"><Avatar size={'medium'} /></div> <div class="flex-center icon">
<span class="ac-header__title">{name}</span> {iconLabel(name)}
</div>
</div>
<span class="wrapped-title">{name}</span>
</div> </div>
<ActionIcon </svelte:fragment>
icon={IconClose}
size={'medium'} <svelte:fragment slot="utils">
action={(_) => { {#if contentType && contentType.startsWith('image/')}
dispatch('close') <Button
}} icon={MaximizeH}
/> kind={'transparent'}
</div> shape={'circle'}
on:click={() => {
imgView = 'img-horizontal-fit'
}}
selected={imgView === 'img-horizontal-fit'}
/>
<Button
icon={MaximizeV}
kind={'transparent'}
shape={'circle'}
on:click={() => {
imgView = 'img-vertical-fit'
}}
selected={imgView === 'img-vertical-fit'}
/>
<Button
icon={MaximizeO}
kind={'transparent'}
shape={'circle'}
on:click={() => {
imgView = 'img-original-fit'
}}
selected={imgView === 'img-original-fit'}
/>
<div class="buttons-divider" />
{/if}
<a class="no-line" href={getFileUrl(file)} download={name}>
<Tooltip label={presentation.string.Download}>
<Button icon={Download} kind={'transparent'} shape={'circle'} />
</Tooltip>
</a>
</svelte:fragment>
{#if contentType && contentType.startsWith('image/')} {#if contentType && contentType.startsWith('image/')}
<div class="pdfviewer-content"> <div class="pdfviewer-content">
@ -58,55 +103,26 @@
{:else} {:else}
<iframe class="pdfviewer-content" src={getFileUrl(file)} title="" /> <iframe class="pdfviewer-content" src={getFileUrl(file)} title="" />
{/if} {/if}
</Panel>
<div class="pdfviewer-footer">
<div class="flex-row-reverse">
<a class="no-line ml-4" href={getFileUrl(file)} download={name}
><Button label={presentation.string.Download} kind={'primary'} /></a
>
<Button
label={presentation.string.Close}
on:click={() => {
dispatch('close')
}}
/>
</div>
{#if contentType && contentType.startsWith('image/')}
<div class="img-nav">
<CircleButton
icon={MaximizeH}
on:click={() => {
imgView = 'img-horizontal-fit'
}}
selected={imgView === 'img-horizontal-fit'}
/>
<CircleButton
icon={MaximizeV}
on:click={() => {
imgView = 'img-vertical-fit'
}}
selected={imgView === 'img-vertical-fit'}
/>
<CircleButton
icon={MaximizeO}
on:click={() => {
imgView = 'img-original-fit'
}}
selected={imgView === 'img-original-fit'}
/>
</div>
{/if}
</div>
</div>
<style lang="scss"> <style lang="scss">
.pdfviewer-container { .icon {
left: 40%; position: relative;
flex-shrink: 0;
width: 2rem;
height: 2rem;
font-weight: 500;
font-size: 0.625rem;
color: var(--white-color);
background-color: var(--primary-bg-color);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
cursor: pointer;
} }
.pdfviewer-content { .pdfviewer-content {
flex-grow: 1; flex-grow: 1;
overflow: auto; overflow: auto;
margin: 0 1.5rem; margin: 1.5rem;
border-style: none; border-style: none;
border-radius: 0.5rem; border-radius: 0.5rem;
background-color: var(--theme-menu-color); background-color: var(--theme-menu-color);
@ -124,14 +140,12 @@
.img-vertical-fit { .img-vertical-fit {
height: 100%; height: 100%;
} }
.pdfviewer-footer { .pdfviewer-header {
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-direction: row-reverse; flex-direction: row-reverse;
align-items: center; align-items: center;
padding: 0 2.25rem;
height: 5.25rem;
} }
.img-nav { .img-nav {
display: grid; display: grid;

View File

@ -0,0 +1,28 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path
d="M12,8.8c-0.2,0-0.4-0.1-0.5-0.4c-0.2-0.5-0.6-1-1-1.3v1.6c0,0.1,0.1,0.1,0.1,0.2c0.2,0.5,0.7,1,1.4,1c0.8,0,1.5,0.7,1.5,1.5 s-0.7,1.5-1.5,1.5H8H4c-0.8,0-1.5-0.7-1.5-1.5S3.2,9.8,4,9.8c0.7,0,1.2-0.5,1.4-1c0-0.1,0.1-0.1,0.1-0.2V7.1c-0.4,0.4-0.8,0.8-1,1.3 C4.4,8.7,4.2,8.8,4,8.8c-1.4,0-2.5,1.1-2.5,2.5s1.1,2.5,2.5,2.5h4h4c1.4,0,2.5-1.1,2.5-2.5S13.4,8.8,12,8.8z"
/>
<path
d="M7.5,3.2v5.5c0,0.3,0.2,0.5,0.5,0.5c0.3,0,0.5-0.2,0.5-0.5V3.2L10.3,5L11,4.3L8.4,1.6L8,1.3L7.6,1.6L8,2l0,0L7.6,1.6L5,4.3 L5.7,5L7.5,3.2z"
/>
</svg>

View File

@ -356,6 +356,7 @@ input.search {
.mb-3 { margin-bottom: .75rem; } .mb-3 { margin-bottom: .75rem; }
.mb-4 { margin-bottom: 1rem; } .mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; } .mb-6 { margin-bottom: 1.5rem; }
.mb-10 { margin-bottom: 2.5rem; }
.mx-1 { margin: 0 .25rem; } .mx-1 { margin: 0 .25rem; }
.mx-2 { margin: 0 .5rem; } .mx-2 { margin: 0 .5rem; }
.mx-3 { margin: 0 .75rem; } .mx-3 { margin: 0 .75rem; }

View File

@ -432,8 +432,7 @@
// THead background-color in Tooltip and Popups // THead background-color in Tooltip and Popups
.popup-tooltip .antiTable .scroller-thead, .popup-tooltip .antiTable .scroller-thead,
.popup .antiTable .scroller-thead, .popup .antiTable .scroller-thead { background-color: var(--accent-bg-color); }
.antiDialogs .antiTable .scroller-thead { background-color: var(--accent-bg-color); }
// Hide row menu in Tooltip // Hide row menu in Tooltip
.popup-tooltip .antiTable .antiTable-body__row:hover .antiTable-cells__firstCell .antiTable-cells__firstCell-menuRow { visibility: hidden; } .popup-tooltip .antiTable .antiTable-body__row:hover .antiTable-cells__firstCell .antiTable-cells__firstCell-menuRow { visibility: hidden; }

View File

@ -13,51 +13,6 @@
// limitations under the License. // limitations under the License.
// //
/* Dialogs */
.antiDialogs {
overflow: hidden;
position: fixed;
top: 32px;
bottom: 1.25rem;
left: 50%;
right: 1rem;
height: calc(100% - 32px - 1.25rem);
background: var(--popup-bg-color);
border-radius: .5rem;
&.fullSize {
flex-direction: row;
left: 1rem;
}
.ac-header.divide { border-bottom: 1px solid var(--theme-card-divider); }
.ad-section-50 {
display: flex;
flex-direction: column;
flex-basis: 50%;
min-height: 0;
width: 50%;
&.divide { border-right: 1px solid var(--theme-card-divider); }
}
.ad-tools {
position: absolute;
display: flex;
top: 1.375rem;
right: 2rem;
}
.tool {
margin-left: 1rem;
color: var(--theme-content-accent-color);
cursor: pointer;
&:hover { color: var(--theme-caption-color); }
}
}
/* Overlays */ /* Overlays */
.antiOverlay { .antiOverlay {
position: fixed; position: fixed;

View File

@ -212,3 +212,24 @@
} }
} }
} }
// Full size state
.panel-instance.fullsize .panel-container {
padding: 0 !important;
}
.panel-instance.fullsize .panel-container .popupPanel,
.popup.fullsize .popupPanel {
border-radius: 0;
box-shadow: none !important;
.popupPanel-title,
.popupPanel-body {
border-radius: 0;
border: none;
}
.popupPanel-title { border-bottom: 1px solid var(--divider-color); }
}
.panel-instance.fullsize,
.popup.fullsize {
transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19) !important;
}

View File

@ -14,12 +14,13 @@
--> -->
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { Button, IconClose, IconDetails } from '..' import { Button, IconClose, IconDetails, IconExpand } from '..'
export let innerWidth: number = 0 export let innerWidth: number = 0
export let panelWidth: number = 0 export let panelWidth: number = 0
export let isHeader: boolean = true export let isHeader: boolean = true
export let isAside: boolean = true export let isAside: boolean = true
export let isFullSize: boolean = false
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -30,8 +31,13 @@
asideFloat = false asideFloat = false
asideShown = false asideShown = false
} }
let docWidth: number
let fullSize: boolean = false
$: if (docWidth <= 900 && !fullSize) fullSize = true
$: if (docWidth > 900 && fullSize) fullSize = false
</script> </script>
<svelte:window bind:innerWidth={docWidth} />
<div class="popupPanel" bind:clientWidth={panelWidth}> <div class="popupPanel" bind:clientWidth={panelWidth}>
<div class="popupPanel-title"> <div class="popupPanel-title">
<Button <Button
@ -45,8 +51,8 @@
<div class="popupPanel-title__content"><slot name="title" /></div> <div class="popupPanel-title__content"><slot name="title" /></div>
<div class="buttons-group xsmall-gap"> <div class="buttons-group xsmall-gap">
<slot name="utils" /> <slot name="utils" />
{#if $$slots.utils}<div class="buttons-divider" />{/if}
{#if asideFloat && $$slots.aside && isAside} {#if asideFloat && $$slots.aside && isAside}
{#if $$slots.utils}<div class="buttons-divider" />{/if}
<Button <Button
icon={IconDetails} icon={IconDetails}
kind={'transparent'} kind={'transparent'}
@ -57,6 +63,19 @@
}} }}
/> />
{/if} {/if}
{#if isFullSize}
<Button
icon={IconExpand}
kind={'transparent'}
size={'medium'}
selected={fullSize}
disabled={docWidth <= 900}
on:click={() => {
fullSize = !fullSize
dispatch('fullsize')
}}
/>
{/if}
</div> </div>
</div> </div>
<div class="popupPanel-body" class:asideShown> <div class="popupPanel-body" class:asideShown>

View File

@ -15,8 +15,9 @@
--> -->
<script lang="ts"> <script lang="ts">
import { getResource } from '@anticrm/platform' import { getResource } from '@anticrm/platform'
import { afterUpdate } from 'svelte' import { afterUpdate, onMount } from 'svelte'
import { AnySvelteComponent, Spinner } from '..' import { Spinner } from '..'
import type { AnySvelteComponent, PopupOptions } from '..'
import { closePanel, PanelProps, panelstore } from '../panelup' import { closePanel, PanelProps, panelstore } from '../panelup'
import { fitPopupElement, popupstore } from '../popups' import { fitPopupElement, popupstore } from '../popups'
@ -24,11 +25,25 @@
let modalHTML: HTMLElement let modalHTML: HTMLElement
let componentInstance: any let componentInstance: any
let docWidth: number
let docSize: boolean = false
let fullSize: boolean = false
let options: { let options: PopupOptions = {
show: boolean props: {
direction: string top: '',
} = { show: false, direction: 'bottom' } bottom: '',
left: '',
right: '',
width: '',
height: '',
maxWidth: '',
maxHeight: '',
minWidth: ''
},
showOverlay: false,
direction: 'bottom'
}
let component: AnySvelteComponent let component: AnySvelteComponent
@ -62,7 +77,17 @@
const fitPopup = (props: PanelProps, contentPanel: HTMLElement): void => { const fitPopup = (props: PanelProps, contentPanel: HTMLElement): void => {
if (modalHTML) { if (modalHTML) {
options = fitPopupElement(modalHTML, props.element, contentPanel) if ((fullSize || docSize) && (props.element === 'float' || props.element === 'content')) {
options = fitPopupElement(modalHTML, 'full')
options.props.width = '100vw'
options.props.maxHeight = 'max-content'
options.showOverlay = false
modalHTML.classList.add('fullsize')
} else {
options = fitPopupElement(modalHTML, props.element, contentPanel)
modalHTML.classList.remove('fullsize')
}
options.fullSize = fullSize
} }
} }
@ -82,9 +107,16 @@
export function fitPopupInstance (): void { export function fitPopupInstance (): void {
if (props) fitPopup(props, contentPanel) if (props) fitPopup(props, contentPanel)
} }
onMount(() => {
if (props) fitPopup(props, contentPanel)
})
$: if (docWidth <= 900 && !docSize) docSize = true
$: if (docWidth > 900 && docSize) docSize = false
</script> </script>
<svelte:window <svelte:window
bind:innerWidth={docWidth}
on:resize={() => { on:resize={() => {
if (props) fitPopup(props, contentPanel) if (props) fitPopup(props, contentPanel)
}} }}
@ -97,7 +129,20 @@
<Spinner /> <Spinner />
{:else} {:else}
<slot name="panel-header" /> <slot name="panel-header" />
<div class="panel-instance" class:bg={props.element === 'content'} bind:this={modalHTML}> <div
class="panel-instance"
class:bg={props.element === 'content'}
bind:this={modalHTML}
style:top={options.props.top}
style:bottom={options.props.bottom}
style:left={options.props.left}
style:right={options.props.right}
style:width={options.props.width}
style:height={options.props.height}
style:max-width={options.props.maxWidth}
style:max-height={options.props.maxHeight}
style:min-width={options.props.minWidth}
>
<div class="panel-container" class:padding={props.element === 'content'}> <div class="panel-container" class:padding={props.element === 'content'}>
<svelte:component <svelte:component
this={component} this={component}
@ -106,15 +151,19 @@
_class={props._class} _class={props._class}
rightSection={props.rightSection} rightSection={props.rightSection}
position={props.element} position={props.element}
bind:options
on:close={_close} on:close={_close}
on:update={_update} on:update={_update}
on:fullsize={() => {
fullSize = !fullSize
}}
/> />
</div> </div>
</div> </div>
{#if props.element !== 'content'} {#if props.element !== 'content'}
<div <div
class="modal-overlay" class="modal-overlay"
class:show={options.show} class:show={options.showOverlay}
on:click={() => escapeClose()} on:click={() => escapeClose()}
on:keydown={() => {}} on:keydown={() => {}}
on:keyup={() => {}} on:keyup={() => {}}
@ -128,6 +177,10 @@
z-index: 401; z-index: 401;
position: fixed; position: fixed;
background-color: transparent; background-color: transparent;
will-change: top, bottom, left, right;
transition-property: top, bottom, left, right, width, height;
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
&.bg { &.bg {
background-color: var(--body-color); background-color: var(--body-color);
@ -154,4 +207,7 @@
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.5);
} }
} }
:global(.panel-instance.fullsize) {
transition-timing-function: cubic-bezier(0.19, 1, 0.22, 1) !important;
}
</style> </style>

View File

@ -14,9 +14,9 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { afterUpdate } from 'svelte' import { afterUpdate, onMount } from 'svelte'
import { fitPopupElement } from '../popups' import { fitPopupElement } from '../popups'
import type { AnyComponent, AnySvelteComponent, PopupAlignment } from '../types' import type { AnyComponent, AnySvelteComponent, PopupAlignment, PopupOptions } from '../types'
export let is: AnyComponent | AnySvelteComponent export let is: AnyComponent | AnySvelteComponent
export let props: object export let props: object
@ -29,12 +29,25 @@
let modalHTML: HTMLElement let modalHTML: HTMLElement
let componentInstance: any let componentInstance: any
let height: number let docWidth: number
let docSize: boolean = false
let fullSize: boolean = false
let options: { let options: PopupOptions = {
show: boolean props: {
direction: string top: '',
} = { show: false, direction: 'bottom' } bottom: '',
left: '',
right: '',
width: '',
height: '',
maxWidth: '',
maxHeight: '',
minWidth: ''
},
showOverlay: false,
direction: 'bottom'
}
function _update (result: any): void { function _update (result: any): void {
if (onUpdate !== undefined) onUpdate(result) if (onUpdate !== undefined) onUpdate(result)
@ -55,7 +68,14 @@
const fitPopup = (): void => { const fitPopup = (): void => {
if (modalHTML) { if (modalHTML) {
options = fitPopupElement(modalHTML, element) if ((fullSize || docSize) && (element === 'float' || element === 'content')) {
options = fitPopupElement(modalHTML, 'full')
modalHTML.classList.add('fullsize')
} else {
options = fitPopupElement(modalHTML, element)
modalHTML.classList.remove('fullsize')
}
options.fullSize = fullSize
} }
} }
@ -64,27 +84,47 @@
escapeClose() escapeClose()
} }
} }
onMount(() => fitPopup())
$: if (docWidth <= 900 && !docSize) docSize = true
$: if (docWidth > 900 && docSize) docSize = false
afterUpdate(() => fitPopup()) afterUpdate(() => fitPopup())
$: if (height) fitPopup()
</script> </script>
<svelte:window on:resize={fitPopup} on:keydown={handleKeydown} /> <svelte:window on:resize={fitPopup} on:keydown={handleKeydown} bind:innerWidth={docWidth} />
<div class="popup" bind:this={modalHTML} bind:clientHeight={height} style={`z-index: ${zIndex + 1};`}> <div
class="popup"
bind:this={modalHTML}
style={`z-index: ${zIndex + 1};`}
style:top={options.props.top}
style:bottom={options.props.bottom}
style:left={options.props.left}
style:right={options.props.right}
style:width={options.props.width}
style:height={options.props.height}
style:max-width={options.props.maxWidth}
style:max-height={options.props.maxHeight}
style:min-width={options.props.minWidth}
>
<svelte:component <svelte:component
this={is} this={is}
bind:this={componentInstance} bind:this={componentInstance}
{...props} {...props}
direction={options.direction} bind:options
on:update={(ev) => { on:update={(ev) => {
_update(ev.detail) _update(ev.detail)
}} }}
on:close={(ev) => _close(ev.detail)} on:close={(ev) => _close(ev.detail)}
on:fullsize={() => {
fullSize = !fullSize
}}
/> />
</div> </div>
<div <div
class="modal-overlay" class="modal-overlay"
class:antiOverlay={options.show} class:antiOverlay={options.showOverlay}
style={`z-index: ${zIndex};`} style={`z-index: ${zIndex};`}
on:click={() => escapeClose()} on:click={() => escapeClose()}
/> />
@ -97,6 +137,10 @@
justify-content: center; justify-content: center;
max-height: calc(100vh - 2rem); max-height: calc(100vh - 2rem);
background-color: transparent; background-color: transparent;
will-change: top, bottom, left, right;
transition-property: top, bottom, left, right, width, height;
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
} }
.modal-overlay { .modal-overlay {
position: fixed; position: fixed;

View File

@ -1,4 +1,4 @@
import type { AnySvelteComponent, AnyComponent, PopupAlignment, PopupPositionElement } from './types' import type { AnySvelteComponent, AnyComponent, PopupAlignment, PopupPositionElement, PopupOptions } from './types'
import { getResource } from '@anticrm/platform' import { getResource } from '@anticrm/platform'
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
@ -117,7 +117,7 @@ export function fitPopupPositionedElement (
modalHTML: HTMLElement, modalHTML: HTMLElement,
alignment: PopupPositionElement, alignment: PopupPositionElement,
newProps: Record<string, string | number> newProps: Record<string, string | number>
): { show: boolean, direction: string } { ): PopupOptions {
let direction: string = '' let direction: string = ''
const rect = alignment.getBoundingClientRect() const rect = alignment.getBoundingClientRect()
const rectPopup = modalHTML.getBoundingClientRect() const rectPopup = modalHTML.getBoundingClientRect()
@ -159,17 +159,17 @@ export function fitPopupPositionedElement (
direction += '|right' direction += '|right'
} }
} }
return { show: false, direction } return { props: newProps, showOverlay: false, direction }
} }
function applyStyle (values: Record<string, string | number>, modalHTML: HTMLElement): void { // function applyStyle (values: Record<string, string | number>, modalHTML: HTMLElement): void {
for (const [k, v] of Object.entries(values)) { // for (const [k, v] of Object.entries(values)) {
const old = (modalHTML.style as any)[k] // const old = (modalHTML.style as any)[k]
if (old !== v) { // if (old !== v) {
;(modalHTML.style as any)[k] = v // ;(modalHTML.style as any)[k] = v
} // }
} // }
} // }
/** /**
* @public * @public
@ -182,7 +182,7 @@ export function fitPopupElement (
modalHTML: HTMLElement, modalHTML: HTMLElement,
element?: PopupAlignment, element?: PopupAlignment,
contentPanel?: HTMLElement contentPanel?: HTMLElement
): { show: boolean, direction: string } { ): PopupOptions {
let show = true let show = true
const newProps: Record<string, string | number> = {} const newProps: Record<string, string | number> = {}
if (element != null) { if (element != null) {
@ -192,7 +192,7 @@ export function fitPopupElement (
newProps.maxWidth = newProps.width = newProps.minWidth = '' newProps.maxWidth = newProps.width = newProps.minWidth = ''
if (typeof element !== 'string') { if (typeof element !== 'string') {
const result = fitPopupPositionedElement(modalHTML, element, newProps) const result = fitPopupPositionedElement(modalHTML, element, newProps)
applyStyle(newProps, modalHTML) // applyStyle(newProps, modalHTML)
return result return result
} else if (element === 'right' && contentPanel !== undefined) { } else if (element === 'right' && contentPanel !== undefined) {
const rect = contentPanel.getBoundingClientRect() const rect = contentPanel.getBoundingClientRect()
@ -209,13 +209,20 @@ export function fitPopupElement (
} else if (element === 'float') { } else if (element === 'float') {
newProps.top = 'calc(var(--status-bar-height) + .25rem)' newProps.top = 'calc(var(--status-bar-height) + .25rem)'
newProps.bottom = '.25rem' newProps.bottom = '.25rem'
newProps.minWidth = '40rem' newProps.left = '60%'
newProps.width = '40%'
newProps.right = '.25rem' newProps.right = '.25rem'
show = true show = true
} else if (element === 'account') { } else if (element === 'account') {
newProps.bottom = '2.75rem' newProps.bottom = '2.75rem'
newProps.left = '5rem' newProps.left = '5rem'
} else if (element === 'full' && contentPanel === undefined) {
newProps.top = '0'
newProps.bottom = '0'
newProps.left = '0'
newProps.right = '0'
// newProps.width = '100vw'
newProps.height = '100vh'
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 + 1}px`
@ -248,8 +255,8 @@ export function fitPopupElement (
newProps.transform = 'translate(-50%, -50%)' newProps.transform = 'translate(-50%, -50%)'
show = true show = true
} }
applyStyle(newProps, modalHTML) // applyStyle(newProps, modalHTML)
return { show, direction: '' } return { props: newProps, showOverlay: show, direction: '' }
} }
export function eventToHTMLElement (evt: MouseEvent): HTMLElement { export function eventToHTMLElement (evt: MouseEvent): HTMLElement {

View File

@ -110,3 +110,10 @@ export interface DropdownIntlItem {
id: string id: string
label: IntlString label: IntlString
} }
export interface PopupOptions {
props: Record<string, string | number>
showOverlay: boolean
direction: string
fullSize?: boolean
}

View File

@ -1,11 +1,11 @@
// This file is read by tools that parse documentation comments conforming to the TSDoc standard. // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
// It should be published with your NPM package. It should not be tracked by Git. // It should be published with your NPM package. It should not be tracked by Git.
{ {
"tsdocVersion": "0.12", "tsdocVersion": "0.12",
"toolPackages": [ "toolPackages": [
{ {
"packageName": "@microsoft/api-extractor", "packageName": "@microsoft/api-extractor",
"packageVersion": "7.23.0" "packageVersion": "7.23.0"
} }
] ]
} }

View File

@ -42,7 +42,7 @@
function openAttachment () { function openAttachment () {
closeTooltip() closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right') showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'float')
} }
</script> </script>

View File

@ -46,7 +46,7 @@
class="flex-center icon" class="flex-center icon"
on:click={() => { on:click={() => {
closeTooltip() closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right') showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'float')
}} }}
> >
{iconLabel(value.name)} {iconLabel(value.name)}
@ -86,7 +86,7 @@
class="name" class="name"
on:click={() => { on:click={() => {
closeTooltip() closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right') showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'float')
}} }}
> >
{trimFilename(value.name)} {trimFilename(value.name)}

View File

@ -33,7 +33,7 @@
class="content flex-center buttonContainer cursor-pointer" class="content flex-center buttonContainer cursor-pointer"
on:click={() => { on:click={() => {
closeTooltip() closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right') showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'float')
}} }}
> >
<img src={getFileUrl(value.file)} alt={value.name} /> <img src={getFileUrl(value.file)} alt={value.name} />

View File

@ -88,7 +88,7 @@
const el: HTMLElement = ev.currentTarget as HTMLElement const el: HTMLElement = ev.currentTarget as HTMLElement
el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }) el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
if (item !== undefined) { if (item !== undefined) {
showPopup(PDFViewer, { file: item.file, name: item.name, contentType: item.type }, 'right') showPopup(PDFViewer, { file: item.file, name: item.name, contentType: item.type }, 'float')
} else { } else {
inputFile.click() inputFile.click()
} }

View File

@ -88,6 +88,8 @@
{object} {object}
isHeader={false} isHeader={false}
isAside={true} isAside={true}
isFullSize
on:fullsize
on:close={() => dispatch('close')} on:close={() => dispatch('close')}
> >
<svelte:fragment slot="navigator"> <svelte:fragment slot="navigator">

View File

@ -45,7 +45,7 @@
function handleClick () { function handleClick () {
closeTooltip() closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right') showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'float')
} }
</script> </script>

View File

@ -64,6 +64,8 @@
<Panel <Panel
isHeader={false} isHeader={false}
isAside={_class === chunter.class.Channel} isAside={_class === chunter.class.Channel}
isFullSize
on:fullsize
on:close={() => { on:close={() => {
dispatch('close') dispatch('close')
}} }}

View File

@ -28,13 +28,14 @@
} from '@anticrm/ui' } from '@anticrm/ui'
import IconCopy from './icons/Copy.svelte' import IconCopy from './icons/Copy.svelte'
import { FocusHandler } from '@anticrm/ui' import { FocusHandler } from '@anticrm/ui'
import type { PopupOptions } from '@anticrm/ui'
import plugin from '../plugin' import plugin from '../plugin'
export let value: string = '' export let value: string = ''
export let placeholder: IntlString export let placeholder: IntlString
export let editable: boolean | undefined = undefined export let editable: boolean | undefined = undefined
export let openable: boolean = false export let openable: boolean = false
export let direction: string = 'bottom' export let options: PopupOptions
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let input: HTMLInputElement let input: HTMLInputElement
@ -75,11 +76,12 @@
input.addEventListener('focus', updateFocus, { once: true }) input.addEventListener('focus', updateFocus, { once: true })
} }
let dir: string = 'bottom'
const vDir = (d: string): string => d.split('|')[0] const vDir = (d: string): string => d.split('|')[0]
const fitEditor = (): void => { const fitEditor = (): void => {
dir = vDir(direction) if (options) dir = vDir(options.direction)
} }
$: dir = vDir(direction) $: if (options) dir = vDir(options.direction)
afterUpdate(() => { afterUpdate(() => {
fitEditor() fitEditor()
}) })

View File

@ -76,6 +76,8 @@
<Panel <Panel
isHeader={true} isHeader={true}
isAside={false} isAside={false}
isFullSize
on:fullsize
on:close={() => { on:close={() => {
dispatch('close') dispatch('close')
}} }}

View File

@ -25,6 +25,7 @@
"GotoLeadApplication": "Switch to Lead Application", "GotoLeadApplication": "Switch to Lead Application",
"IssueDescriptionPlaceholder": "Add description...", "IssueDescriptionPlaceholder": "Add description...",
"CreateCustomer": "Create Customer", "CreateCustomer": "Create Customer",
"CreateCustomerLabel": "New Customer" "CreateCustomerLabel": "New Customer",
"NoLeadsForDocument": "No leads for document"
} }
} }

View File

@ -25,6 +25,7 @@
"GotoLeadApplication": "Открыть приложение Сделки", "GotoLeadApplication": "Открыть приложение Сделки",
"IssueDescriptionPlaceholder": "Добавить описание...", "IssueDescriptionPlaceholder": "Добавить описание...",
"CreateCustomer": "Добавить Клиента", "CreateCustomer": "Добавить Клиента",
"CreateCustomerLabel": "Новый Клиент" "CreateCustomerLabel": "Новый Клиент",
"NoLeadsForDocument": "Нет потенциальных клиентов для документа"
} }
} }

View File

@ -538,7 +538,7 @@
focusIndex={103} focusIndex={103}
icon={FileIcon} icon={FileIcon}
on:click={() => { on:click={() => {
showPopup(PDFViewer, { file: resume.uuid, name: resume.name }, 'right') showPopup(PDFViewer, { file: resume.uuid, name: resume.name }, 'float')
}} }}
> >
<svelte:fragment slot="content"> <svelte:fragment slot="content">

View File

@ -58,6 +58,8 @@
isHeader={false} isHeader={false}
isAside={true} isAside={true}
{object} {object}
isFullSize
on:fullsize
on:close={() => { on:close={() => {
dispatch('close') dispatch('close')
}} }}

View File

@ -54,6 +54,8 @@
isHeader={false} isHeader={false}
isAside={true} isAside={true}
{object} {object}
isFullSize
on:fullsize
on:close={() => { on:close={() => {
dispatch('close') dispatch('close')
}} }}

View File

@ -20,7 +20,7 @@
import type { DoneState, Kanban, SpaceWithStates, State } from '@anticrm/task' import type { DoneState, Kanban, SpaceWithStates, State } from '@anticrm/task'
import task from '../../plugin' import task from '../../plugin'
import KanbanEditor from '../kanban/KanbanEditor.svelte' import KanbanEditor from '../kanban/KanbanEditor.svelte'
import { Icon, IconClose, Label, showPopup, ActionIcon, ScrollBox } from '@anticrm/ui' import { Icon, Label, showPopup, Panel, Scroller } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import workbench from '@anticrm/workbench' import workbench from '@anticrm/workbench'
@ -91,39 +91,35 @@
} }
</script> </script>
<div <Panel
class="antiOverlay" isHeader={false}
on:click={() => { isAside={false}
isFullSize
on:fullsize
on:close={() => {
dispatch('close') dispatch('close')
}} }}
/> >
<div class="antiDialogs antiComponent"> <svelte:fragment slot="title">
<div class="ac-header short mirror divide"> <div class="antiTitle icon-wrapper">
<div class="ac-header__wrap-description"> <div class="wrapped-icon">
<div class="ac-header__wrap-title"> <Icon icon={task.icon.ManageStatuses} size={'small'} />
<div class="ac-header__icon"><Icon icon={task.icon.ManageStatuses} size={'small'} /></div> </div>
<span class="ac-header__title"> <div class="title-wrapper">
<span class="wrapped-title">
<Label label={task.string.ManageStatusesWithin} /> <Label label={task.string.ManageStatusesWithin} />
{#if spaceClassInstance}<Label label={spaceClassInstance?.label} />{:else}...{/if} {#if spaceClassInstance}<Label label={spaceClassInstance?.label} />{:else}...{/if}
</span> </span>
{#if spaceInstance?.name}<span class="wrapped-subtitle">{spaceInstance?.name}</span>{/if}
</div> </div>
{#if spaceInstance?.name}<span class="ac-header__description">{spaceInstance?.name}</span>{/if}
</div> </div>
<div class="tool"> </svelte:fragment>
<ActionIcon
icon={IconClose} <Scroller>
size={'small'} <div class="popupPanel-body__main-content py-10 clear-mins">
action={() => {
dispatch('close')
}}
/>
</div>
</div>
<div class="p-10 flex-grow">
<ScrollBox vertical stretch>
{#if kanban !== undefined} {#if kanban !== undefined}
<KanbanEditor {kanban} on:delete={(e) => deleteState(e.detail)} /> <KanbanEditor {kanban} on:delete={(e) => deleteState(e.detail)} />
{/if} {/if}
</ScrollBox> </div>
</div> </Scroller>
</div> </Panel>

View File

@ -75,68 +75,66 @@
} }
</script> </script>
<div> <div class="flex-no-shrink flex-between trans-title uppercase">
<div class="flex-no-shrink flex-between trans-title uppercase"> <Label label={task.string.ActiveStates} />
<Label label={task.string.ActiveStates} /> <CircleButton
<CircleButton icon={IconAdd}
icon={IconAdd} size={'medium'}
size={'medium'} on:click={() => {
on:click={() => { onAdd(task.class.State)
onAdd(task.class.State) }}
}} />
/> </div>
</div> <div class="mt-3">
<div class="mt-3"> {#each states as state, i}
{#each states as state, i} {#if state}
{#if state} <div
bind:this={elements[i]}
class="flex-between states"
draggable={true}
on:dragover|preventDefault={(ev) => {
dragover(ev, i)
}}
on:drop|preventDefault={() => {
onMove(i)
}}
on:dragstart={() => {
selected = i
dragState = states[i]._id
}}
on:dragend={() => {
selected = undefined
}}
>
<div class="bar"><Circles /></div>
<div <div
bind:this={elements[i]} class="color"
class="flex-between states" style="background-color: {getPlatformColor(state.color)}"
draggable={true} on:click={() => {
on:dragover|preventDefault={(ev) => { showPopup(ColorsPopup, {}, elements[i], onColorChange(state))
dragover(ev, i)
}} }}
on:drop|preventDefault={() => { />
onMove(i) <div class="flex-grow caption-color">
}} <AttributeEditor maxWidth={'20rem'} _class={state._class} object={state} key="title" />
on:dragstart={() => {
selected = i
dragState = states[i]._id
}}
on:dragend={() => {
selected = undefined
}}
>
<div class="bar"><Circles /></div>
<div
class="color"
style="background-color: {getPlatformColor(state.color)}"
on:click={() => {
showPopup(ColorsPopup, {}, elements[i], onColorChange(state))
}}
/>
<div class="flex-grow caption-color">
<AttributeEditor maxWidth={'20rem'} _class={state._class} object={state} key="title" />
</div>
{#if states.length > 1}
<div
class="tool hover-trans"
on:click={(ev) => {
showPopup(
StatusesPopup,
{ onDelete: () => dispatch('delete', { state }) },
eventToHTMLElement(ev),
() => {}
)
}}
>
<IconMoreH size={'medium'} />
</div>
{/if}
</div> </div>
{/if} {#if states.length > 1}
{/each} <div
</div> class="tool hover-trans"
on:click={(ev) => {
showPopup(
StatusesPopup,
{ onDelete: () => dispatch('delete', { state }) },
eventToHTMLElement(ev),
() => {}
)
}}
>
<IconMoreH size={'medium'} />
</div>
{/if}
</div>
{/if}
{/each}
</div> </div>
<div class="mt-9"> <div class="mt-9">
<div class="flex-no-shrink flex-between trans-title uppercase"> <div class="flex-no-shrink flex-between trans-title uppercase">
@ -189,7 +187,7 @@
}} }}
/> />
</div> </div>
<div class="mt-4"> <div class="mt-4 mb-10">
{#each lostStates as state} {#each lostStates as state}
{#if state} {#if state}
<div class="states flex-row-center"> <div class="states flex-row-center">
@ -222,10 +220,10 @@
<style lang="scss"> <style lang="scss">
.states { .states {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
color: var(--theme-caption-color); color: var(--caption-color);
background-color: var(--theme-button-bg-enabled); background-color: var(--button-bg-color);
border: 1px solid var(--theme-bg-accent-color); border: 1px solid var(--button-border-color);
border-radius: 0.75rem; border-radius: 0.5rem;
user-select: none; user-select: none;
.bar { .bar {

View File

@ -43,7 +43,7 @@ import AssignedTasks from './components/AssignedTasks.svelte'
import task from './plugin' import task from './plugin'
async function editStatuses (object: SpaceWithStates): Promise<void> { async function editStatuses (object: SpaceWithStates): Promise<void> {
showPopup(EditStatuses, { _id: object._id, spaceClass: object._class }, 'right') showPopup(EditStatuses, { _id: object._id, spaceClass: object._class }, 'float')
} }
export async function queryTask<D extends Task> ( export async function queryTask<D extends Task> (

View File

@ -188,6 +188,8 @@
<Panel <Panel
isHeader={true} isHeader={true}
isAside={false} isAside={false}
isFullSize
on:fullsize
on:close={() => { on:close={() => {
dispatch('close') dispatch('close')
}} }}

View File

@ -139,6 +139,8 @@
isSub={false} isSub={false}
withoutActivity={isEditing} withoutActivity={isEditing}
bind:innerWidth bind:innerWidth
isFullSize
on:fullsize
on:close={() => dispatch('close')} on:close={() => dispatch('close')}
> >
<svelte:fragment slot="subtitle"> <svelte:fragment slot="subtitle">

View File

@ -228,6 +228,8 @@
isAside={true} isAside={true}
bind:panelWidth bind:panelWidth
bind:innerWidth bind:innerWidth
isFullSize
on:fullsize
on:update={(ev) => _update(ev.detail)} on:update={(ev) => _update(ev.detail)}
on:close={() => { on:close={() => {
dispatch('close') dispatch('close')

View File

@ -55,6 +55,8 @@
<Panel <Panel
isHeader={false} isHeader={false}
isAside={false} isAside={false}
isFullSize
on:fullsize
on:close={() => { on:close={() => {
dispatch('close') dispatch('close')
}} }}