mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Update Panel layout. (#1591)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
21f0928e71
commit
c4267cc7ae
@ -19,49 +19,108 @@
|
||||
import type { Doc } from '@anticrm/core'
|
||||
import notification from '@anticrm/notification'
|
||||
import type { Asset } from '@anticrm/platform'
|
||||
import { AnyComponent, AnySvelteComponent, Component, Panel } from '@anticrm/ui'
|
||||
import { AnyComponent, AnySvelteComponent, Component, Panel, Icon } from '@anticrm/ui'
|
||||
|
||||
export let title: string | undefined = undefined
|
||||
export let subtitle: string | undefined = undefined
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let showHeader: boolean = true
|
||||
export let rightSection: AnyComponent | undefined = undefined
|
||||
export let object: Doc
|
||||
export let panelWidth: number = 0
|
||||
export let innerWidth: number = 0
|
||||
export let isSubtitle: boolean = false
|
||||
export let isProperties: boolean = false
|
||||
export let isHeader: boolean = true
|
||||
export let isSub: boolean = true
|
||||
export let isAside: boolean = true
|
||||
export let minimize: boolean = false
|
||||
|
||||
let docWidth: number = 0
|
||||
$: minimize = docWidth < 1280 && docWidth >= 1024
|
||||
$: needHeader = $$slots.header || minimize || isHeader
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth={docWidth} />
|
||||
<Panel
|
||||
{title}
|
||||
{subtitle}
|
||||
{icon}
|
||||
rightSection={rightSection !== undefined}
|
||||
{showHeader}
|
||||
bind:isAside
|
||||
isHeader={needHeader}
|
||||
bind:panelWidth
|
||||
bind:innerWidth
|
||||
isProperties={innerWidth >= 500 || isProperties}
|
||||
isSubtitle={innerWidth < 900 || isSubtitle}
|
||||
on:close
|
||||
>
|
||||
<svelte:fragment slot="subtitle">
|
||||
{#if $$slots.subtitle}<slot name="subtitle" />{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="properties">
|
||||
{#if $$slots.properties}<slot name="properties" />{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="navigate-actions">
|
||||
<slot name="navigate-actions" />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="commands">
|
||||
<div class="buttons-group xsmall-gap">
|
||||
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
|
||||
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
|
||||
<svelte:fragment slot="title">
|
||||
<div class="popupPanel-title__content-container antiTitle">
|
||||
{#if $$slots.navigator}
|
||||
<div class="buttons-group xsmall-gap mr-4">
|
||||
<slot name="navigator" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $$slots.title}
|
||||
<slot name="title" />
|
||||
{:else}
|
||||
<div class="icon-wrapper">
|
||||
{#if icon}<div class="wrapped-icon"><Icon {icon} size={'medium'} /></div>{/if}
|
||||
<div class="title-wrapper">
|
||||
{#if title}<span class="wrapped-title">{title}</span>{/if}
|
||||
{#if subtitle}<span class="wrapped-subtitle">{subtitle}</span>{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<slot name="actions" />
|
||||
</svelte:fragment>
|
||||
<Component is={rightSection ?? activity.component.Activity} props={{ object, integrate: true }}>
|
||||
|
||||
<svelte:fragment slot="utils">
|
||||
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
|
||||
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
|
||||
{#if $$slots.utils}
|
||||
<div class="buttons-divider" />
|
||||
<slot name="utils" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="header">
|
||||
{#if $$slots.header || ($$slots.actions && minimize)}
|
||||
<div class="header-row between">
|
||||
{#if $$slots.header}<slot name="header" />{/if}
|
||||
<div class="buttons-group xsmall-gap ml-4">
|
||||
<slot name="tools" />
|
||||
{#if $$slots.actions && minimize}
|
||||
<div class="buttons-divider" />
|
||||
<slot name="actions" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $$slots['custom-attributes']}
|
||||
{#if isSub}<div class="header-row"><slot name="custom-attributes" direction="row" /></div>{/if}
|
||||
{:else}
|
||||
{#if $$slots.attributes && minimize}<div class="header-row"><slot name="attributes" direction="row" /></div>{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
<div style="padding: .75rem 1.5rem">
|
||||
{#if $$slots.actions}
|
||||
<div class="flex-row-center pb-3 bottom-divider">
|
||||
<span class="fs-bold w-24 mr-6"><slot name="actions-label" /></span>
|
||||
<div class="buttons-group xsmall-gap">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $$slots['custom-attributes']}
|
||||
<slot name="custom-attributes" direction="column" />
|
||||
{:else}
|
||||
{#if $$slots.attributes}<slot name="attributes" direction="column" />{/if}
|
||||
{/if}
|
||||
{#if $$slots.aside}<slot name="aside" />{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
{#if rightSection !== undefined}
|
||||
<slot />
|
||||
</Component>
|
||||
{:else}
|
||||
<Component is={activity.component.Activity} props={{ object, integrate: true }}>
|
||||
<slot />
|
||||
</Component>
|
||||
{/if}
|
||||
</Panel>
|
||||
|
@ -80,6 +80,8 @@
|
||||
--popup-bg-hover: #37373c;
|
||||
--popup-divider: #313236;
|
||||
--popup-shadow: rgb(0 0 0 / 20%) 0px 4px 24px;
|
||||
--popup-panel-shadow: rgb(0 0 0 / 55%) 0px 7px 24px;
|
||||
--popup-aside-shadow: rgb(0 0 0 / 25%) 0px 8px 16px;
|
||||
--card-shadow: rgb(0 0 0 / 50%) 0px 16px 70px;
|
||||
--card-overlay-color: rgba(28, 29, 31, .5);
|
||||
--avatar-bg-color: #4f5358;
|
||||
@ -220,6 +222,7 @@
|
||||
--popup-bg-hover: #f8f9fb;
|
||||
--popup-divider: #eff1f4;
|
||||
--popup-shadow: rgb(0 0 0 / 20%) 0px 4px 24px; // Dark
|
||||
--popup-panel-shadow: rgb(0 0 0 / 55%) 0px 7px 24px; // Dark
|
||||
--card-shadow: rgb(0 0 0 / 50%) 0px 16px 70px;
|
||||
--card-overlay-color: rgba(144, 149, 157, .4);
|
||||
--avatar-bg-color: #e0e0e0; // HZ
|
||||
|
@ -263,6 +263,12 @@ p:last-child { margin-block-end: 0; }
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
}
|
||||
.buttons-divider {
|
||||
min-width: 1px;
|
||||
width: 1px;
|
||||
height: 1.5rem;
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
|
||||
.gap-1, .gap-1-5, .gap-2 {
|
||||
& > *:not(:last-child) { margin-right: .25rem; }
|
||||
@ -365,6 +371,7 @@ p:last-child { margin-block-end: 0; }
|
||||
.pb-3 { padding-bottom: .75rem; }
|
||||
.pb-4 { padding-bottom: 1rem; }
|
||||
.px-2 { padding: 0 .5rem; }
|
||||
.px-3 { padding: 0 .75rem; }
|
||||
.px-4 { padding: 0 1rem; }
|
||||
|
||||
.p-1 { padding: .25rem; }
|
||||
@ -419,6 +426,7 @@ p:last-child { margin-block-end: 0; }
|
||||
.h-7 { height: 1.75rem; }
|
||||
.h-8 { height: 2rem; }
|
||||
.h-9 { height: 2.25rem; }
|
||||
.h-14 { height: 3.5rem; }
|
||||
.h-16 { height: 4rem; }
|
||||
.h-18 { height: 4.5rem; }
|
||||
.w-full { width: 100%; }
|
||||
|
@ -202,3 +202,48 @@
|
||||
.antiNav-bottomFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 1px, rgba(0, 0, 0, 1) calc(100% - 2rem), rgba(0, 0, 0, 0) 100%); }
|
||||
.antiNav-bothFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 2rem, rgba(0, 0, 0, 1) calc(100% - 2rem), rgba(0, 0, 0, 0) 100%); }
|
||||
.antiNav-noneFade { mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, 1) 1px, rgba(0, 0, 0, 1) calc(100% - 1px), rgba(0, 0, 0, 0) 100%); }
|
||||
|
||||
/* Basic */
|
||||
.antiTitle {
|
||||
.icon-wrapper,
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
.title-wrapper {
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.icon-wrapper { align-items: center; }
|
||||
|
||||
.wrapped-icon {
|
||||
margin-right: .75rem;
|
||||
color: var(--content-color);
|
||||
}
|
||||
.wrapped-title {
|
||||
min-width: 0;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
color: var(--caption-color);
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
}
|
||||
.wrapped-subtitle {
|
||||
min-width: 0;
|
||||
font-size: 0.75rem;
|
||||
color: var(--dark-color);
|
||||
|
||||
overflow: hidden;
|
||||
visibility: visible;
|
||||
display: -webkit-box;
|
||||
/* autoprefixer: ignore next */
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
@ -55,3 +55,159 @@
|
||||
}
|
||||
}
|
||||
|
||||
.popupPanel {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
border-radius: .5rem;
|
||||
box-shadow: var(--popup-panel-shadow);
|
||||
|
||||
.popupPanel-title {
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
padding: .5rem .75rem;
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
max-height: 3rem;
|
||||
background-color: var(--board-card-bg-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
border-bottom: none;
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
|
||||
&__content {
|
||||
flex-grow: 1;
|
||||
margin: 0 .75rem;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
&-container {
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popupPanel-body {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: stretch;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--body-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 0 0 .5rem .5rem;
|
||||
|
||||
&__main, &__aside {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__main {
|
||||
flex-grow: 2;
|
||||
flex-basis: 760px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 auto;
|
||||
padding: .75rem 1.25rem;
|
||||
width: calc(100% - 5rem);
|
||||
min-width: 0;
|
||||
max-width: 900px;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
|
||||
&.between { justify-content: space-between; }
|
||||
}
|
||||
.header-row + .header-row { margin-top: .625rem; }
|
||||
}
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: calc(100% - 7.5rem);
|
||||
height: 100%;
|
||||
max-width: 860px;
|
||||
}
|
||||
}
|
||||
|
||||
&__aside {
|
||||
position: relative;
|
||||
width: 25%;
|
||||
min-width: 320px;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 1rem;
|
||||
bottom: 1rem;
|
||||
left: 0;
|
||||
width: 0;
|
||||
border-left: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
&.float {
|
||||
position: absolute;
|
||||
flex-shrink: 0;
|
||||
top: 0;
|
||||
left: 100%;
|
||||
width: 320px;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
background-color: var(--board-card-bg-color);
|
||||
border-top: 1px solid var(--board-card-bg-color);
|
||||
border-left: 1px solid var(--divider-color);
|
||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
|
||||
transition: box-shadow 150ms ease 0s, transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
transform: translateX(0);
|
||||
|
||||
&::before { content: none; }
|
||||
&.shown {
|
||||
box-shadow: var(--popup-aside-shadow);
|
||||
transform: translateX(-100%);
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 0;
|
||||
left: 100%;
|
||||
width: 360px;
|
||||
height: 100%;
|
||||
background-color: var(--board-card-bg-color);
|
||||
border: 1px solid var(--board-card-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.asideShown {
|
||||
border: none;
|
||||
|
||||
.popupPanel-body__main {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 0 0 .5rem .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
export let width: string | undefined = undefined
|
||||
export let resetIconSize: boolean = false
|
||||
export let highlight: boolean = false
|
||||
export let selected: boolean = false
|
||||
export let focus: boolean = false
|
||||
export let click: boolean = false
|
||||
export let title: string | undefined = undefined
|
||||
@ -60,6 +61,7 @@
|
||||
class:border-radius-2={shape === 'round'}
|
||||
class:border-radius-4={shape === 'circle'}
|
||||
class:highlight
|
||||
class:selected
|
||||
disabled={disabled || loading}
|
||||
style={width ? 'width: ' + width : ''}
|
||||
{title}
|
||||
@ -215,9 +217,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&.transparent:hover {
|
||||
background-color: var(--button-bg-hover);
|
||||
}
|
||||
&.transparent:hover,
|
||||
&.transparent.selected { background-color: var(--button-bg-hover); }
|
||||
&.link {
|
||||
padding: 0 0.875rem;
|
||||
&:hover {
|
||||
|
@ -13,152 +13,65 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Asset } from '@anticrm/platform'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { AnySvelteComponent } from '../types'
|
||||
import Button from './Button.svelte'
|
||||
import Icon from './Icon.svelte'
|
||||
import IconClose from './icons/Close.svelte'
|
||||
import { Button, IconClose, IconDetails, Scroller } from '..'
|
||||
|
||||
export let title: string | undefined = undefined
|
||||
export let subtitle: string | undefined = undefined
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let rightSection: boolean = false
|
||||
export let showHeader: boolean = true
|
||||
export let innerWidth: number = 0
|
||||
export let panelWidth: number = 0
|
||||
export let isSubtitle: boolean = true
|
||||
export let isProperties: boolean = true
|
||||
export let isHeader: boolean = true
|
||||
export let isAside: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let asideFloat: boolean = false
|
||||
let asideShown: boolean = false
|
||||
let docWidth: number
|
||||
$: if (docWidth < 1024 && !asideFloat) asideFloat = true
|
||||
$: if (docWidth >= 1024 && asideFloat) {
|
||||
asideFloat = false
|
||||
asideShown = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiPanel antiComponent" bind:clientWidth={panelWidth}>
|
||||
<div class="panel-content-container">
|
||||
{#if showHeader}
|
||||
<div class="ac-header short mirror divide highlight">
|
||||
<div class="buttons-group">
|
||||
<Button
|
||||
icon={IconClose}
|
||||
size={'medium'}
|
||||
kind={'transparent'}
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
{#if $$slots['navigate-actions']}
|
||||
<div class="buttons-group xsmall-gap">
|
||||
<slot name="navigate-actions" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ml-4 ac-header__wrap-title flex-grow">
|
||||
{#if icon}
|
||||
<div class="ac-header__icon">
|
||||
<Icon {icon} size={'large'} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="ac-header__wrap-description">
|
||||
{#if title}<span class="ac-header__title">{title}</span>{/if}
|
||||
{#if subtitle}<span class="ac-header__description">{subtitle}</span>{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons-group xsmall-gap">
|
||||
<slot name="commands" />
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="ac-header short mirror divide highlight">
|
||||
<div class="buttons-group">
|
||||
<Button
|
||||
icon={IconClose}
|
||||
size={'medium'}
|
||||
kind={'transparent'}
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
{#if $$slots['navigate-actions']}
|
||||
<div class="buttons-group xsmall-gap">
|
||||
<slot name="navigate-actions" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $$slots['custom-title']}
|
||||
<div class="ml-4 flex-row-center flex-grow">
|
||||
<slot name="custom-title" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="main-content" class:withProperties={$$slots.properties} bind:clientWidth={innerWidth}>
|
||||
{#if $$slots.subtitle && $$slots.properties && isSubtitle}
|
||||
<div class="flex-col flex-grow clear-mins">
|
||||
<div class="ac-subtitle">
|
||||
<div class="ac-subtitle-content">
|
||||
<slot name="subtitle" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col flex-grow clear-mins">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex-col flex-grow clear-mins">
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
{#if rightSection}
|
||||
<slot name="rightSection" />
|
||||
{/if}
|
||||
{#if $$slots.properties && isProperties}
|
||||
<div class="properties-container">
|
||||
<slot name="properties" />
|
||||
</div>
|
||||
<svelte:window bind:innerWidth={docWidth} />
|
||||
<div class="popupPanel" bind:clientWidth={panelWidth}>
|
||||
<div class="popupPanel-title">
|
||||
<Button icon={IconClose} kind={'transparent'} size={'medium'} on:click={() => { dispatch('close') }} />
|
||||
<div class="popupPanel-title__content"><slot name="title" /></div>
|
||||
<div class="buttons-group xsmall-gap">
|
||||
<slot name="utils" />
|
||||
{#if asideFloat}
|
||||
{#if $$slots.utils}<div class="buttons-divider" />{/if}
|
||||
<Button
|
||||
icon={IconDetails}
|
||||
kind={'transparent'}
|
||||
size={'medium'}
|
||||
selected={asideShown}
|
||||
on:click={() => {
|
||||
asideShown = !asideShown
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="popupPanel-body" class:asideShown>
|
||||
<div class="popupPanel-body__main" bind:clientWidth={innerWidth}>
|
||||
{#if $$slots.header && isHeader}
|
||||
<div class="popupPanel-body__main-header">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
{/if}
|
||||
<Scroller>
|
||||
<div class="popupPanel-body__main-content">
|
||||
<slot />
|
||||
</div>
|
||||
</Scroller>
|
||||
</div>
|
||||
{#if $$slots.aside && isAside}
|
||||
<div class="popupPanel-body__aside" class:float={asideFloat} class:shown={asideShown}>
|
||||
<slot name="aside" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.panel-content-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex-grow: 1;
|
||||
// height: 100%;
|
||||
|
||||
&.withProperties {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.properties-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
min-width: 20rem;
|
||||
width: 20rem;
|
||||
border-left: 1px solid var(--divider-color);
|
||||
// background-color: var(--board-card-bg-color);
|
||||
|
||||
// &::before {
|
||||
// position: absolute;
|
||||
// content: '';
|
||||
// top: 1.5rem;
|
||||
// bottom: 1.5rem;
|
||||
// left: 0;
|
||||
// width: 1px;
|
||||
// background-color: var(--divider-color);
|
||||
// }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
25
packages/ui/src/components/icons/Details.svelte
Normal file
25
packages/ui/src/components/icons/Details.svelte
Normal file
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
// 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: 'x-small' | '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 fill-rule="evenodd" clip-rule="evenodd" d="M 4.25 2 C 2.45508 2 1 3.45508 1 5.25 V 10.7499 C 1 12.5449 2.45508 13.9999 4.25 13.9999 H 11.75 C 13.5449 13.9999 15 12.5449 15 10.7499 V 5.25 C 15 3.45508 13.5449 2 11.75 2 H 4.25 Z M 2.5 10.4999 C 2.5 11.6045 3.39543 12.4999 4.5 12.4999 H 11.75 C 12.7165 12.4999 13.5 11.7164 13.5 10.7499 V 5.25 C 13.5 4.28351 12.7165 3.5 11.75 3.5 H 4.5 C 3.39543 3.5 2.5 4.39543 2.5 5.5 V 10.4999 Z" />
|
||||
<rect x="9" y="3" width="1.5" height="10" />
|
||||
</svg>
|
@ -119,6 +119,7 @@ export { default as IconNavNext } from './components/icons/NavNext.svelte'
|
||||
export { default as IconDPCalendar } from './components/calendar/icons/DPCalendar.svelte'
|
||||
export { default as IconDPCalendarOver } from './components/calendar/icons/DPCalendarOver.svelte'
|
||||
export { default as IconOptions } from './components/icons/Options.svelte'
|
||||
export { default as IconDetails } from './components/icons/Details.svelte'
|
||||
|
||||
export { default as PanelInstance } from './components/PanelInstance.svelte'
|
||||
export { default as Panel } from './components/Panel.svelte'
|
||||
|
@ -195,6 +195,13 @@ export function fitPopupElement (modalHTML: HTMLElement, element?: PopupAlignmen
|
||||
newProps.left = '50%'
|
||||
newProps.transform = 'translateX(-50%)'
|
||||
show = true
|
||||
} else if (element === 'float') {
|
||||
newProps.top = 'calc(var(--status-bar-height) + .25rem)'
|
||||
newProps.bottom = '.25rem'
|
||||
newProps.width = '40rem'
|
||||
newProps.maxWidth = '40%'
|
||||
newProps.right = '.25rem'
|
||||
show = true
|
||||
} else if (element === 'account') {
|
||||
newProps.bottom = '2.75rem'
|
||||
newProps.left = '5rem'
|
||||
|
@ -72,7 +72,7 @@ export interface PopupPositionElement {
|
||||
h: HorizontalAlignment
|
||||
}
|
||||
}
|
||||
export type PopupAlignment = PopupPositionElement | null | 'right' | 'top' | 'account' | 'full' | 'content' | 'middle'
|
||||
export type PopupAlignment = PopupPositionElement | null | 'right' | 'top' | 'float' | 'account' | 'full' | 'content' | 'middle'
|
||||
export type TooltipAlignment = 'top' | 'bottom' | 'left' | 'right'
|
||||
export type VerticalAlignment = 'top' | 'bottom'
|
||||
export type HorizontalAlignment = 'left' | 'right'
|
||||
|
@ -82,31 +82,29 @@
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<Scroller>
|
||||
<div class="p-10 bottom-highlight-select">
|
||||
<slot />
|
||||
<div class="mt-4 pb-4 bottom-highlight-select">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="flex-row-center h-14 px-3 mt-4 antiTitle">
|
||||
<div class="icon-wrapper">
|
||||
<div class="wrapped-icon icon flex-center"><IconActivity size={'small'} /></div>
|
||||
<span class="wrapped-title"><Label label={activity.string.Activity} /></span>
|
||||
</div>
|
||||
<div class="ac-header short mirror-tool mt-2">
|
||||
<div class="ac-header__wrap-title">
|
||||
<div class="flex-center icon"><IconActivity size={'small'} /></div>
|
||||
<span class="ac-header__title"><Label label={activity.string.Activity} /></span>
|
||||
</div>
|
||||
</div>
|
||||
{#if showCommenInput}
|
||||
<div class="ref-input">
|
||||
<Component is={chunter.component.CommentInput} props={{ object }} />
|
||||
</div>
|
||||
{#if showCommenInput}
|
||||
<div class="ref-input">
|
||||
<Component is={chunter.component.CommentInput} props={{ object }} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="p-activity">
|
||||
{#if txes}
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
{#each txes as tx}
|
||||
<TxView {tx} {viewlets} />
|
||||
{/each}
|
||||
</Grid>
|
||||
{/if}
|
||||
<div class="p-activity">
|
||||
{#if txes}
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
{#each txes as tx}
|
||||
<TxView {tx} {viewlets} />
|
||||
{/each}
|
||||
</Grid>
|
||||
{/if}
|
||||
</div>
|
||||
</Scroller>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@ -120,10 +118,11 @@
|
||||
}
|
||||
.ref-input {
|
||||
flex-shrink: 0;
|
||||
padding: 1.5rem 2.5rem;
|
||||
padding: 1.5rem 0;
|
||||
|
||||
}
|
||||
.p-activity {
|
||||
padding: 1.5rem 2.5rem;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
:global(.grid .msgactivity-container:last-child::after) {
|
||||
|
@ -22,7 +22,7 @@
|
||||
import type { State } from '@anticrm/task'
|
||||
import task from '@anticrm/task'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { EditBox, Icon, Label } from '@anticrm/ui'
|
||||
import { Button, EditBox, Icon, Label } from '@anticrm/ui'
|
||||
import { UpDownNavigator } from '@anticrm/view-resources'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import board from '../plugin'
|
||||
@ -72,12 +72,21 @@
|
||||
onMount(() => {
|
||||
dispatch('open', { ignoreKeys: ['comments', 'number', 'title'] })
|
||||
})
|
||||
let minimize: boolean = false
|
||||
</script>
|
||||
|
||||
{#if object !== undefined}
|
||||
<Panel icon={board.icon.Card} title={object?.title} {object} on:close={() => dispatch('close')}>
|
||||
<svelte:fragment slot="navigate-actions">
|
||||
<UpDownNavigator element={object} />
|
||||
<Panel
|
||||
icon={board.icon.Card}
|
||||
title={object?.title}
|
||||
{object}
|
||||
bind:minimize
|
||||
isHeader={minimize}
|
||||
isAside={!minimize}
|
||||
on:close={() => dispatch('close')}
|
||||
>
|
||||
<svelte:fragment slot="navigator">
|
||||
<UpDownNavigator element={object}/>
|
||||
</svelte:fragment>
|
||||
|
||||
<!-- TODO cover -->
|
||||
@ -128,8 +137,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="properties">
|
||||
<div class="p-4"><CardActions bind:value={object} /></div>
|
||||
<svelte:fragment slot="custom-attributes" let:direction>
|
||||
{#if direction === 'column'}
|
||||
<CardActions bind:value={object} />
|
||||
{:else}
|
||||
<Button icon={board.icon.Card} label={board.string.Actions} kind={'no-border'} size={'small'} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Panel>
|
||||
{/if}
|
||||
|
@ -18,7 +18,6 @@
|
||||
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
||||
import ChannelsDropdown from './ChannelsDropdown.svelte'
|
||||
import { showPanel } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let value: Channel[] | Channel | null
|
||||
|
||||
@ -32,7 +31,13 @@
|
||||
if (ev.detail.presenter !== undefined && Array.isArray(value)) {
|
||||
const channel = value[0]
|
||||
if (channel !== undefined) {
|
||||
showPanel(view.component.EditDoc, channel.attachedTo, channel.attachedToClass, 'content', ev.detail.presenter)
|
||||
showPanel(
|
||||
ev.detail.presenter,
|
||||
channel.attachedTo,
|
||||
channel.attachedToClass,
|
||||
'float',
|
||||
ev.detail.presenter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@
|
||||
"@anticrm/attachment": "~0.6.1",
|
||||
"@anticrm/attachment-resources": "~0.6.0",
|
||||
"@anticrm/login": "~0.6.1",
|
||||
"@anticrm/core": "~0.6.16"
|
||||
"@anticrm/core": "~0.6.16",
|
||||
"@anticrm/panel": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -195,6 +195,7 @@
|
||||
border-bottom: 1px solid var(--theme-zone-bg);
|
||||
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
margin-right: 1rem;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
@ -206,6 +207,6 @@
|
||||
|
||||
.right-content {
|
||||
flex-grow: 1;
|
||||
padding: 1.5rem 1rem;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -14,31 +14,49 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Channel, Contact } from '@anticrm/contact'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import { Ref, Doc, Class } from '@anticrm/core'
|
||||
import contact, { Channel } from '@anticrm/contact'
|
||||
import { SharedMessage } from '@anticrm/gmail'
|
||||
import NewMessage from './NewMessage.svelte'
|
||||
import FullMessage from './FullMessage.svelte'
|
||||
import Chats from './Chats.svelte'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { Panel } from '@anticrm/panel'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let object: Contact
|
||||
export let _id: Ref<Doc>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let rightSection: AnyComponent | undefined = undefined
|
||||
|
||||
// export let object: Contact
|
||||
// $: console.log('!!!!!!!!!!!! id: ', _id, ' - class: ', _class)
|
||||
let object: any
|
||||
let newMessage: boolean = false
|
||||
let currentMessage: SharedMessage | undefined = undefined
|
||||
let channel: Channel | undefined = undefined
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
client
|
||||
.findOne(contact.class.Channel, {
|
||||
attachedTo: object._id,
|
||||
attachedTo: _id,
|
||||
provider: contact.channelProvider.Email
|
||||
})
|
||||
.then((res) => {
|
||||
channel = res
|
||||
})
|
||||
|
||||
const query = createQuery()
|
||||
$: _id &&
|
||||
_class &&
|
||||
query.query(_class, { _id }, (result) => {
|
||||
object = result[0]
|
||||
})
|
||||
|
||||
function back () {
|
||||
if (newMessage) {
|
||||
return (newMessage = false)
|
||||
@ -54,12 +72,24 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if channel}
|
||||
{#if newMessage}
|
||||
<NewMessage {object} {channel} {currentMessage} on:close={back} />
|
||||
{:else if currentMessage}
|
||||
<FullMessage {currentMessage} bind:newMessage on:close={back} />
|
||||
{:else}
|
||||
<Chats {object} {channel} bind:newMessage on:select={selectHandler} />
|
||||
{/if}
|
||||
{#if channel && object}
|
||||
<Panel
|
||||
icon={contact.icon.Email}
|
||||
title={'Email'}
|
||||
{rightSection}
|
||||
{object}
|
||||
isHeader={false}
|
||||
isAside={false}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
{#if newMessage}
|
||||
<NewMessage {object} {channel} {currentMessage} on:close={back} />
|
||||
{:else if currentMessage}
|
||||
<FullMessage {currentMessage} bind:newMessage on:close={back} />
|
||||
{:else}
|
||||
<Chats {object} {channel} bind:newMessage on:select={selectHandler} />
|
||||
{/if}
|
||||
</Panel>
|
||||
{/if}
|
||||
|
@ -43,6 +43,7 @@
|
||||
"@anticrm/core": "~0.6.16",
|
||||
"@anticrm/notification-resources": "~0.6.0",
|
||||
"@anticrm/attachment": "~0.6.1",
|
||||
"@anticrm/attachment-resources": "~0.6.0"
|
||||
"@anticrm/attachment-resources": "~0.6.0",
|
||||
"@anticrm/panel": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,15 @@
|
||||
<script lang="ts">
|
||||
import attachment from '@anticrm/attachment'
|
||||
import { AttachmentRefInput } from '@anticrm/attachment-resources'
|
||||
import contact, { Channel, Contact, EmployeeAccount, formatName } from '@anticrm/contact'
|
||||
import { generateId, getCurrentAccount, Ref, SortingOrder, Space } from '@anticrm/core'
|
||||
import { Panel } from '@anticrm/panel'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import contact, { Channel, EmployeeAccount, formatName } from '@anticrm/contact'
|
||||
import { generateId, getCurrentAccount, Ref, SortingOrder, Space, Doc, Class } from '@anticrm/core'
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import setting, { Integration } from '@anticrm/setting'
|
||||
import type { NewTelegramMessage, SharedTelegramMessage, TelegramMessage } from '@anticrm/telegram'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import { Button, eventToHTMLElement, IconShare, Tooltip, Scroller, showPopup } from '@anticrm/ui'
|
||||
import telegram from '../plugin'
|
||||
import Connect from './Connect.svelte'
|
||||
@ -29,22 +32,35 @@
|
||||
import Messages from './Messages.svelte'
|
||||
import Reconnect from './Reconnect.svelte'
|
||||
|
||||
export let object: Contact
|
||||
export let _id: Ref<Doc>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let rightSection: AnyComponent | undefined = undefined
|
||||
|
||||
let object: any
|
||||
let channel: Channel | undefined = undefined
|
||||
let objectId: Ref<NewTelegramMessage> = generateId()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const client = getClient()
|
||||
const notificationClient = NotificationClientImpl.getClient()
|
||||
|
||||
client
|
||||
.findOne(contact.class.Channel, {
|
||||
attachedTo: object._id,
|
||||
attachedTo: _id,
|
||||
provider: contact.channelProvider.Telegram
|
||||
})
|
||||
.then((res) => {
|
||||
channel = res
|
||||
})
|
||||
|
||||
const query = createQuery()
|
||||
$: _id &&
|
||||
_class &&
|
||||
query.query(_class, { _id }, (result) => {
|
||||
object = result[0]
|
||||
})
|
||||
|
||||
let messages: TelegramMessage[] = []
|
||||
let accounts: EmployeeAccount[] = []
|
||||
let integration: Integration | undefined
|
||||
@ -171,111 +187,101 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="telegram-header">
|
||||
<div class="ac-header__wrap-title">
|
||||
<div class="flex-center icon"><TelegramIcon size={'small'} /></div>
|
||||
<div class="ac-header__wrap-description">
|
||||
<span class="ac-header__title">Telegram</span>
|
||||
<span class="ac-header__description">You and {formatName(object.name)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip label={telegram.string.Share}>
|
||||
<Button
|
||||
icon={IconShare}
|
||||
kind={'transparent'}
|
||||
size={'medium'}
|
||||
on:click={async () => {
|
||||
selectable = !selectable
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="telegram-content" class:selectable>
|
||||
<Scroller bottomStart autoscroll>
|
||||
{#if messages && accounts}
|
||||
<Messages messages={convertMessages(messages, accounts)} {selectable} bind:selected />
|
||||
{/if}
|
||||
</Scroller>
|
||||
</div>
|
||||
{#if object !== undefined}
|
||||
<Panel
|
||||
icon={TelegramIcon}
|
||||
title={'Telegram'}
|
||||
{rightSection}
|
||||
{object}
|
||||
isHeader={false}
|
||||
isAside={false}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
You and {formatName(object.name)}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="tools">
|
||||
<Tooltip label={telegram.string.Share}>
|
||||
<Button
|
||||
icon={IconShare}
|
||||
kind={'transparent'}
|
||||
size={'medium'}
|
||||
on:click={async () => {
|
||||
selectable = !selectable
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="ref-input" class:selectable>
|
||||
{#if selectable}
|
||||
<div class="flex-between">
|
||||
<span>{selected.size} messages selected</span>
|
||||
<div class="flex">
|
||||
<div>
|
||||
<Button label={telegram.string.Cancel} size={'medium'} on:click={clear} />
|
||||
<div class="telegram-content" class:selectable>
|
||||
<Scroller bottomStart autoscroll>
|
||||
{#if messages && accounts}
|
||||
<Messages messages={convertMessages(messages, accounts)} {selectable} bind:selected />
|
||||
{/if}
|
||||
</Scroller>
|
||||
</div>
|
||||
|
||||
<div class="ref-input" class:selectable>
|
||||
{#if selectable}
|
||||
<div class="flex-between">
|
||||
<span>{selected.size} messages selected</span>
|
||||
<div class="flex">
|
||||
<div>
|
||||
<Button label={telegram.string.Cancel} size={'medium'} on:click={clear} />
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<Button
|
||||
label={telegram.string.PublishSelected}
|
||||
size={'medium'}
|
||||
kind={'primary'}
|
||||
disabled={!selected.size}
|
||||
on:click={share}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
{:else if integration === undefined}
|
||||
<div class="flex-center">
|
||||
<Button
|
||||
label={telegram.string.PublishSelected}
|
||||
size={'medium'}
|
||||
label={telegram.string.Connect}
|
||||
kind={'primary'}
|
||||
disabled={!selected.size}
|
||||
on:click={share}
|
||||
on:click={(e) => {
|
||||
showPopup(Connect, {}, eventToHTMLElement(e), onConnectClose)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if integration.disabled}
|
||||
<div class="flex-center">
|
||||
<Button
|
||||
label={setting.string.Reconnect}
|
||||
kind={'primary'}
|
||||
on:click={(e) => {
|
||||
showPopup(Reconnect, {}, eventToHTMLElement(e), onReconnect)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<AttachmentRefInput
|
||||
space={telegram.space.Telegram}
|
||||
_class={telegram.class.NewMessage}
|
||||
{objectId}
|
||||
on:message={onMessage}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if integration === undefined}
|
||||
<div class="flex-center">
|
||||
<Button
|
||||
label={telegram.string.Connect}
|
||||
kind={'primary'}
|
||||
on:click={(e) => {
|
||||
showPopup(Connect, {}, eventToHTMLElement(e), onConnectClose)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else if integration.disabled}
|
||||
<div class="flex-center">
|
||||
<Button
|
||||
label={setting.string.Reconnect}
|
||||
kind={'primary'}
|
||||
on:click={(e) => {
|
||||
showPopup(Reconnect, {}, eventToHTMLElement(e), onReconnect)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<AttachmentRefInput
|
||||
space={telegram.space.Telegram}
|
||||
_class={telegram.class.NewMessage}
|
||||
{objectId}
|
||||
on:message={onMessage}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</Panel>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.telegram-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
margin: 0 2.5rem;
|
||||
padding: 0;
|
||||
height: 4rem;
|
||||
min-height: 4rem;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
.icon {
|
||||
margin-right: 1rem;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
color: var(--white-color);
|
||||
background-color: var(--primary-bg-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.ref-input {
|
||||
padding: 0 2.5rem 1.5rem;
|
||||
padding: 0 0 1.5rem;
|
||||
|
||||
&.selectable {
|
||||
padding: 1rem 2.5rem;
|
||||
padding: 1rem 0;
|
||||
color: var(--theme-caption-color);
|
||||
background-color: var(--accent-bg-color);
|
||||
border-top: 1px solid var(--theme-card-divider);
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,13 +80,16 @@
|
||||
onMount(() => {
|
||||
dispatch('open', { ignoreKeys: ['comments', 'name', 'description', 'number'] })
|
||||
})
|
||||
let minimize: boolean = false
|
||||
</script>
|
||||
|
||||
{#if issue !== undefined}
|
||||
<Panel
|
||||
object={issue}
|
||||
showHeader={false}
|
||||
isSubtitle={true}
|
||||
bind:minimize
|
||||
isHeader
|
||||
isAside={!minimize}
|
||||
isSub={minimize}
|
||||
bind:innerWidth
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
@ -106,115 +109,135 @@
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="navigate-actions">
|
||||
<svelte:fragment slot="navigator">
|
||||
<Button icon={IconDownOutline} kind={'secondary'} size={'medium'} />
|
||||
<Button icon={IconUpOutline} kind={'secondary'} size={'medium'} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="header">
|
||||
<span class="fs-title">{issueLabel}</span>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="tools">
|
||||
<Button icon={IconEdit} kind={'transparent'} size={'medium'} />
|
||||
<Button icon={IconMoreH} kind={'transparent'} size={'medium'} />
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="flex-col flex-grow flex-no-shrink h-full mx-auto content">
|
||||
<div class="mt-6">
|
||||
<EditBox
|
||||
label={tracker.string.Title}
|
||||
bind:value={issue.title}
|
||||
placeholder={tracker.string.IssueTitlePlaceholder}
|
||||
maxWidth={'16rem'}
|
||||
focus
|
||||
on:change={() => change('title', issue?.title)}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<StyledTextBox
|
||||
alwaysEdit
|
||||
bind:content={issue.description}
|
||||
placeholder={tracker.string.IssueDescriptionPlaceholder}
|
||||
on:value={(evt) => change('description', evt.detail)}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<EditBox
|
||||
label={tracker.string.Title}
|
||||
bind:value={issue.title}
|
||||
placeholder={tracker.string.IssueTitlePlaceholder}
|
||||
maxWidth={'16rem'}
|
||||
focus
|
||||
on:change={() => change('title', issue?.title)}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6 mb-6">
|
||||
<StyledTextBox
|
||||
alwaysEdit
|
||||
bind:content={issue.description}
|
||||
placeholder={tracker.string.IssueDescriptionPlaceholder}
|
||||
on:value={(evt) => change('description', evt.detail)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="properties">
|
||||
{#if issue && currentTeam && issueStatuses}
|
||||
<div class="flex-grow relative min-w-80 right-panel">
|
||||
<div class="ac-header short divide header">
|
||||
<span class="w-24 overflow-label">{issueLabel}</span>
|
||||
<div class="buttons-group">
|
||||
<Button
|
||||
icon={tracker.icon.Issue}
|
||||
title={tracker.string.CopyIssueUrl}
|
||||
width="min-content"
|
||||
size="small"
|
||||
kind="transparent"
|
||||
on:click={() => copy(window.location.href)}
|
||||
/>
|
||||
<Button
|
||||
icon={tracker.icon.Views}
|
||||
title={tracker.string.CopyIssueId}
|
||||
width="min-content"
|
||||
size="small"
|
||||
kind="transparent"
|
||||
on:click={() => issueLabel && copy(issueLabel)}
|
||||
/>
|
||||
</div>
|
||||
<span slot="actions-label">{issueLabel}</span>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
icon={tracker.icon.Issue}
|
||||
title={tracker.string.CopyIssueUrl}
|
||||
width="min-content"
|
||||
size="small"
|
||||
kind="transparent"
|
||||
on:click={() => copy(window.location.href)}
|
||||
/>
|
||||
<Button
|
||||
icon={tracker.icon.Views}
|
||||
title={tracker.string.CopyIssueId}
|
||||
width="min-content"
|
||||
size="small"
|
||||
kind="transparent"
|
||||
on:click={() => issueLabel && copy(issueLabel)}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="custom-attributes" let:direction>
|
||||
{#if issue && currentTeam && issueStatuses && direction === 'column'}
|
||||
<div class="content mt-4">
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Status} />
|
||||
</span>
|
||||
<StatusPresenter value={issue} statuses={issueStatuses} currentSpace={currentTeam._id} shouldShowLabel />
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Status} />
|
||||
</span>
|
||||
<StatusPresenter value={issue} statuses={issueStatuses} currentSpace={currentTeam._id} shouldShowLabel />
|
||||
</div>
|
||||
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Priority} />
|
||||
</span>
|
||||
<PriorityPresenter value={issue} currentSpace={currentTeam._id} shouldShowLabel />
|
||||
</div>
|
||||
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Assignee} />
|
||||
</span>
|
||||
<UserBox
|
||||
_class={contact.class.Employee}
|
||||
label={tracker.string.Assignee}
|
||||
placeholder={tracker.string.Assignee}
|
||||
bind:value={issue.assignee}
|
||||
allowDeselect
|
||||
titleDeselect={tracker.string.Unassigned}
|
||||
on:change={() => change('assignee', issue?.assignee)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Labels} />
|
||||
</span>
|
||||
<Button
|
||||
label={tracker.string.Labels}
|
||||
icon={tracker.icon.Labels}
|
||||
width="min-content"
|
||||
size="small"
|
||||
kind="no-border"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="devider" />
|
||||
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Project} />
|
||||
</span>
|
||||
<Button
|
||||
label={tracker.string.Project}
|
||||
icon={tracker.icon.Projects}
|
||||
width="min-content"
|
||||
size="small"
|
||||
kind="no-border"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Priority} />
|
||||
</span>
|
||||
<PriorityPresenter value={issue} currentSpace={currentTeam._id} shouldShowLabel />
|
||||
</div>
|
||||
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Assignee} />
|
||||
</span>
|
||||
<UserBox
|
||||
_class={contact.class.Employee}
|
||||
label={tracker.string.Assignee}
|
||||
placeholder={tracker.string.Assignee}
|
||||
bind:value={issue.assignee}
|
||||
allowDeselect
|
||||
titleDeselect={tracker.string.Unassigned}
|
||||
size="large"
|
||||
kind="link"
|
||||
on:change={() => change('assignee', issue?.assignee)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Labels} />
|
||||
</span>
|
||||
<Button
|
||||
label={tracker.string.Labels}
|
||||
icon={tracker.icon.Labels}
|
||||
width="max-content"
|
||||
size="large"
|
||||
kind="link"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="devider" />
|
||||
|
||||
<div class="flex-row-center mb-4">
|
||||
<span class="label w-24">
|
||||
<Label label={tracker.string.Project} />
|
||||
</span>
|
||||
<Button
|
||||
label={tracker.string.Project}
|
||||
icon={tracker.icon.Projects}
|
||||
width="fit-content"
|
||||
size="large"
|
||||
kind="link"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="buttons-group small-gap">
|
||||
<Button
|
||||
label={tracker.string.Labels}
|
||||
icon={tracker.icon.Labels}
|
||||
width="min-content"
|
||||
size="small"
|
||||
kind="no-border"
|
||||
/>
|
||||
<Button
|
||||
label={tracker.string.Project}
|
||||
icon={tracker.icon.Projects}
|
||||
width="min-content"
|
||||
size="small"
|
||||
kind="no-border"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
@ -222,37 +245,23 @@
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
max-width: 56.25rem;
|
||||
width: calc(100% - 5rem);
|
||||
justify-content: space-between;
|
||||
padding: 0 1.25rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
.header {
|
||||
padding: 1rem 0;
|
||||
margin: 0 1.5rem;
|
||||
}
|
||||
.content {
|
||||
position: absolute;
|
||||
inset: 2.5rem 0 0;
|
||||
padding: 1.5rem 0.5rem 1.5rem 1.5rem;
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
inset: 2.5rem 0 0;
|
||||
padding: 1.5rem 0.5rem 1.5rem 1.5rem;
|
||||
|
||||
.label {
|
||||
margin: 0.625rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.devider {
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
margin: 0.75rem 1.5rem 1.25rem 0;
|
||||
.label {
|
||||
margin: 0.625rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.devider {
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
margin: 0.75rem 1.5rem 1.25rem 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { ChannelProvider, formatName } from '@anticrm/contact'
|
||||
import core, { Class, ClassifierKind, Doc, getCurrentAccount, Hierarchy, Mixin, Obj, Ref, Space } from '@anticrm/core'
|
||||
import core, { Class, ClassifierKind, Doc, getCurrentAccount, Mixin, Obj, Ref, Space } from '@anticrm/core'
|
||||
import notification from '@anticrm/notification'
|
||||
import { Panel } from '@anticrm/panel'
|
||||
import { Asset, getResource, IntlString, translate } from '@anticrm/platform'
|
||||
@ -27,8 +27,7 @@
|
||||
KeyedAttribute
|
||||
} from '@anticrm/presentation'
|
||||
import setting, { IntegrationType } from '@anticrm/setting'
|
||||
import { AnyComponent, Button, Component, IconActivity } from '@anticrm/ui'
|
||||
import Tooltip from '@anticrm/ui/src/components/Tooltip.svelte'
|
||||
import { AnyComponent, Component } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { getCollectionCounter } from '../utils'
|
||||
@ -38,7 +37,6 @@
|
||||
export let _id: Ref<Doc>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let rightSection: AnyComponent | undefined = undefined
|
||||
// export let position: PopupAlignment | undefined = undefined
|
||||
|
||||
let lastId: Ref<Doc> = _id
|
||||
let lastClass: Ref<Class<Doc>> = _class
|
||||
@ -49,7 +47,6 @@
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const notificationClient = getResource(notification.function.GetNotificationClient).then((res) => res())
|
||||
|
||||
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
|
||||
|
||||
$: read(_id)
|
||||
@ -76,18 +73,11 @@
|
||||
|
||||
$: if (object !== undefined) objectClass = hierarchy.getClass(object._class)
|
||||
|
||||
let selectedClass: Ref<Class<Doc>> | undefined
|
||||
let prevSelected = selectedClass
|
||||
|
||||
let keys: KeyedAttribute[] = []
|
||||
let collectionEditors: { key: KeyedAttribute; editor: AnyComponent }[] = []
|
||||
|
||||
let mixins: Mixin<Doc>[] = []
|
||||
|
||||
$: if (object && prevSelected !== object._class) {
|
||||
prevSelected = object._class
|
||||
selectedClass = Hierarchy.mixinOrClass(object)
|
||||
|
||||
$: if (object) {
|
||||
parentClass = getParentClass(object._class)
|
||||
getMixins()
|
||||
}
|
||||
@ -119,7 +109,11 @@
|
||||
let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
|
||||
|
||||
async function updateKeys (): Promise<void> {
|
||||
const filtredKeys = getFiltredKeys(selectedClass ?? object._class, ignoreKeys)
|
||||
let filtredKeys = getFiltredKeys(object._class, ignoreKeys)
|
||||
for (const m of mixins) {
|
||||
const mkeys = getFiltredKeys(m._id, ignoreKeys)
|
||||
filtredKeys = filtredKeys.concat(mkeys).filter((it, idx, arr) => arr.indexOf(it) === idx)
|
||||
}
|
||||
keys = collectionsFilter(filtredKeys, false)
|
||||
|
||||
const collectionKeys = collectionsFilter(filtredKeys, true)
|
||||
@ -151,9 +145,7 @@
|
||||
}
|
||||
|
||||
let mainEditor: AnyComponent
|
||||
|
||||
$: if (object) getEditorOrDefault(selectedClass, object._class)
|
||||
|
||||
$: if (object) getEditorOrDefault(object._class, object._class)
|
||||
async function getEditorOrDefault (_class: Ref<Class<Doc>> | undefined, defaultClass: Ref<Class<Doc>>): Promise<void> {
|
||||
let editor = _class !== undefined ? await getEditor(_class) : undefined
|
||||
if (editor === undefined) {
|
||||
@ -286,6 +278,8 @@
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
let minimize: boolean = false
|
||||
</script>
|
||||
|
||||
<ActionContext
|
||||
@ -300,6 +294,9 @@
|
||||
{title}
|
||||
{rightSection}
|
||||
{object}
|
||||
bind:minimize
|
||||
isHeader={false}
|
||||
isAside={!minimize}
|
||||
bind:panelWidth
|
||||
bind:innerWidth
|
||||
on:update={(ev) => _update(ev.detail)}
|
||||
@ -307,56 +304,20 @@
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="navigate-actions">
|
||||
<svelte:fragment slot="navigator">
|
||||
<UpDownNavigator element={object} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
|
||||
<svelte:fragment slot="attributes" let:direction={dir}>
|
||||
{#if !headerLoading}
|
||||
{#if headerEditor !== undefined}
|
||||
<Component is={headerEditor} props={{ object, keys }} />
|
||||
<Component is={headerEditor} props={{ object, keys, vertical: dir === 'column' }} />
|
||||
{:else}
|
||||
<AttributesBar {object} {keys} />
|
||||
<AttributesBar {object} {keys} vertical={dir === 'column'} />
|
||||
{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="properties">
|
||||
{#if !headerLoading}
|
||||
<div class="p-4">
|
||||
{#if headerEditor !== undefined}
|
||||
<Component is={headerEditor} props={{ object, keys, vertical: true }} />
|
||||
{:else}
|
||||
<AttributesBar {object} {keys} vertical />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if displayedIntegrations && displayedIntegrations.length > 0}
|
||||
<div class="actions-divider" />
|
||||
{#each displayedIntegrations as pr}
|
||||
<Tooltip label={pr.label}>
|
||||
<Button
|
||||
icon={pr.icon}
|
||||
size={'medium'}
|
||||
kind={'transparent'}
|
||||
highlight={rightSection === pr.presenter}
|
||||
on:click={() => {
|
||||
if (rightSection !== pr.presenter) rightSection = pr.presenter
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
<Button
|
||||
icon={IconActivity}
|
||||
size={'medium'}
|
||||
kind={'transparent'}
|
||||
highlight={!rightSection}
|
||||
on:click={() => {
|
||||
if (rightSection) rightSection = undefined
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="main-editor">
|
||||
{#if mainEditor}
|
||||
<Component
|
||||
@ -376,7 +337,7 @@
|
||||
</div>
|
||||
{#each collectionEditors as collection}
|
||||
{#if collection.editor}
|
||||
<div class="mt-14">
|
||||
<div class="mt-6">
|
||||
<Component
|
||||
is={collection.editor}
|
||||
props={{
|
||||
@ -400,12 +361,4 @@
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.actions-divider {
|
||||
margin: 0 0.25rem;
|
||||
min-width: 1px;
|
||||
width: 1px;
|
||||
height: 1.5rem;
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
</style>
|
||||
|
@ -298,12 +298,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
let navFloat: boolean = !(window.innerWidth < 1100)
|
||||
let navFloat: boolean = window.innerWidth < 1024 ? false : true
|
||||
const windowResize = (): void => {
|
||||
if (window.innerWidth < 1100 && !navFloat) {
|
||||
if (window.innerWidth < 1024 && !navFloat) {
|
||||
visibileNav = false
|
||||
navFloat = true
|
||||
} else if (window.innerWidth >= 1100 && navFloat) {
|
||||
} else if (window.innerWidth >= 1024 && navFloat) {
|
||||
navFloat = false
|
||||
visibileNav = true
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user