mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Unify Popup and Panel behaviour (#1347)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
9b9c785da5
commit
662cca9282
@ -238,7 +238,7 @@
|
||||
}
|
||||
.kanban-content {
|
||||
display: flex;
|
||||
margin: 1.5rem 2rem;
|
||||
margin: 0.5rem 2rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
@ -15,20 +15,17 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { getClient } from '../utils'
|
||||
import { Label, showPopup, Button, Tooltip } from '@anticrm/ui'
|
||||
import type { TooltipAligment } from '@anticrm/ui'
|
||||
import Avatar from './Avatar.svelte'
|
||||
import UsersPopup from './UsersPopup.svelte'
|
||||
import UserInfo from './UserInfo.svelte'
|
||||
import IconPerson from './icons/Person.svelte'
|
||||
|
||||
import type { Ref, Class } from '@anticrm/core'
|
||||
import contact, { Contact, formatName } from '@anticrm/contact'
|
||||
import type { Class, Ref } from '@anticrm/core'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import type { TooltipAligment } from '@anticrm/ui'
|
||||
import { Button, Label, showPopup, Tooltip } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import presentation from '..'
|
||||
import { getClient } from '../utils'
|
||||
import IconPerson from './icons/Person.svelte'
|
||||
import UserInfo from './UserInfo.svelte'
|
||||
import UsersPopup from './UsersPopup.svelte'
|
||||
|
||||
export let _class: Ref<Class<Contact>>
|
||||
export let label: IntlString
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Person } from '@anticrm/contact'
|
||||
import { Person } from '@anticrm/contact'
|
||||
import type { Class, Doc, Ref } from '@anticrm/core'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { ActionIcon, CircleButton, IconAdd, IconClose, Label, ShowMore, showPopup } from '@anticrm/ui'
|
||||
@ -78,7 +78,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#each persons as person}
|
||||
<div class="antiComponentBox flex-center margin_025 antiComponentBoxFocused">
|
||||
<div class="antiComponentBox flex-center antiComponentBoxFocused">
|
||||
<UserInfo value={person} size={'x-small'} />
|
||||
<div class="ml-1">
|
||||
<ActionIcon icon={IconClose} size={'small'} action={() => removePerson(person)} />
|
||||
@ -93,16 +93,13 @@
|
||||
|
||||
<style lang="scss">
|
||||
.persons-container {
|
||||
padding: 0.5rem;
|
||||
color: var(--theme-caption-color);
|
||||
background: var(--theme-bg-accent-color);
|
||||
border: 1px solid var(--theme-bg-accent-color);
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
.person-items {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
.margin_025 {
|
||||
margin: 0.25rem;
|
||||
|
@ -324,6 +324,7 @@ p:last-child { margin-block-end: 0; }
|
||||
|
||||
.p-2 { padding: .5rem; }
|
||||
.p-3 { padding: .75rem; }
|
||||
.p-4 { padding: 1rem; }
|
||||
.p-6 { padding: 1.5rem; }
|
||||
.p-10 { padding: 2.5rem; }
|
||||
|
||||
|
@ -39,7 +39,7 @@
|
||||
.ad-tools {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: 1rem;
|
||||
top: 1.375rem;
|
||||
right: 2rem;
|
||||
&.grow-reverse {
|
||||
left: 0px;
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { afterUpdate } from 'svelte'
|
||||
import { AnySvelteComponent, Spinner } from '..'
|
||||
import { closePanel, PanelProps, panelstore } from '../panelup'
|
||||
import { popupstore } from '../popups'
|
||||
import { fitPopupElement, popupstore } from '../popups'
|
||||
|
||||
export let contentPanel: HTMLElement
|
||||
|
||||
@ -55,67 +55,7 @@
|
||||
|
||||
const fitPopup = (props: PanelProps, contentPanel: HTMLElement): void => {
|
||||
if (modalHTML) {
|
||||
if (props.element) {
|
||||
show = false
|
||||
modalHTML.style.left = modalHTML.style.right = modalHTML.style.top = modalHTML.style.bottom = ''
|
||||
modalHTML.style.maxHeight = modalHTML.style.height = ''
|
||||
if (typeof props.element !== 'string') {
|
||||
const el = props.element as HTMLElement
|
||||
const rect = el.getBoundingClientRect()
|
||||
const rectPopup = modalHTML.getBoundingClientRect()
|
||||
// Vertical
|
||||
if (rect.bottom + rectPopup.height + 28 <= document.body.clientHeight) {
|
||||
modalHTML.style.top = `calc(${rect.bottom}px + .75rem)`
|
||||
} else if (rectPopup.height + 28 < rect.top) {
|
||||
modalHTML.style.bottom = `calc(${document.body.clientHeight - rect.y}px + .75rem)`
|
||||
} else {
|
||||
modalHTML.style.top = modalHTML.style.bottom = '1rem'
|
||||
}
|
||||
|
||||
// Horizontal
|
||||
if (rect.left + rectPopup.width + 16 > document.body.clientWidth) {
|
||||
modalHTML.style.right = document.body.clientWidth - rect.right + 'px'
|
||||
} else {
|
||||
modalHTML.style.left = rect.left + 'px'
|
||||
}
|
||||
} else if (props.element === 'right' && contentPanel !== undefined) {
|
||||
const rect = contentPanel.getBoundingClientRect()
|
||||
modalHTML.style.top = `calc(${rect.top}px + 0.5rem)`
|
||||
modalHTML.style.bottom = '0.75rem'
|
||||
modalHTML.style.right = '0.75rem'
|
||||
} else if (props.element === 'top') {
|
||||
modalHTML.style.top = '15vh'
|
||||
modalHTML.style.left = '50%'
|
||||
modalHTML.style.transform = 'translateX(-50%)'
|
||||
show = true
|
||||
} else if (props.element === 'account') {
|
||||
modalHTML.style.bottom = '2.75rem'
|
||||
modalHTML.style.left = '5rem'
|
||||
} else if (props.element === 'full' && contentPanel !== undefined) {
|
||||
const rect = contentPanel.getBoundingClientRect()
|
||||
modalHTML.style.top = `calc(${rect.top}px + 0.5rem)`
|
||||
modalHTML.style.bottom = '0.75rem'
|
||||
modalHTML.style.left = '0.75rem'
|
||||
modalHTML.style.right = '0.75rem'
|
||||
} else if (props.element === 'content' && contentPanel !== undefined) {
|
||||
const rect = contentPanel.getBoundingClientRect()
|
||||
modalHTML.style.top = `calc(${rect.top}px + 0.5rem)`
|
||||
modalHTML.style.bottom = '0.75rem'
|
||||
modalHTML.style.left = `calc(${rect.left}px + 0.5rem)`
|
||||
modalHTML.style.right = '0.75rem'
|
||||
} else if (props.element === 'middle' && contentPanel !== undefined) {
|
||||
const rect = contentPanel.getBoundingClientRect()
|
||||
modalHTML.style.top = `calc(${rect.top}px + 0.5rem)`
|
||||
modalHTML.style.bottom = '0.75rem'
|
||||
modalHTML.style.left = '50%'
|
||||
modalHTML.style.transform = 'translateX(-50%)'
|
||||
}
|
||||
} else {
|
||||
modalHTML.style.top = '50%'
|
||||
modalHTML.style.left = '50%'
|
||||
modalHTML.style.transform = 'translate(-50%, -50%)'
|
||||
show = true
|
||||
}
|
||||
show = fitPopupElement(modalHTML, props.element, contentPanel)
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,25 +80,27 @@
|
||||
{#if props}
|
||||
{#if !component}
|
||||
<Spinner />
|
||||
{:else}
|
||||
<div class="antiPanel panel-instance" bind:this={modalHTML}>
|
||||
<svelte:component
|
||||
this={component}
|
||||
bind:this={componentInstance}
|
||||
_id={props._id}
|
||||
_class={props._class}
|
||||
rightSection={props.rightSection}
|
||||
position={props.element }
|
||||
on:close={_close}
|
||||
on:update={() => {
|
||||
if (props) {
|
||||
fitPopup(props, contentPanel)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="panel-instance" class:bg={props.element === 'content'} bind:this={modalHTML}>
|
||||
<div class="p-2 w-full h-full">
|
||||
<svelte:component
|
||||
this={component}
|
||||
bind:this={componentInstance}
|
||||
_id={props._id}
|
||||
_class={props._class}
|
||||
rightSection={props.rightSection}
|
||||
position={props.element}
|
||||
on:close={_close}
|
||||
on:update={() => {
|
||||
if (props) {
|
||||
fitPopup(props, contentPanel)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if props.element !== 'content'}
|
||||
<div class="modal-overlay" class:show style={'z-index: 400'} on:click={() => escapeClose()} />
|
||||
<div class="modal-overlay" class:show on:click={() => escapeClose()} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
@ -168,8 +110,12 @@
|
||||
z-index: 401;
|
||||
position: fixed;
|
||||
background-color: transparent;
|
||||
&.bg {
|
||||
background-color: var(--theme-bg-color);
|
||||
}
|
||||
}
|
||||
.modal-overlay {
|
||||
z-index: 400;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -177,6 +123,6 @@
|
||||
height: 100%;
|
||||
&.show {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { afterUpdate } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import type { AnySvelteComponent, AnyComponent, PopupAlignment } from '../types'
|
||||
import { fitPopupElement } from '../popups'
|
||||
import type { AnyComponent, AnySvelteComponent, PopupAlignment } from '../types'
|
||||
|
||||
export let is: AnyComponent | AnySvelteComponent
|
||||
export let props: object
|
||||
@ -51,53 +51,7 @@
|
||||
|
||||
const fitPopup = (): void => {
|
||||
if (modalHTML) {
|
||||
if (element) {
|
||||
show = false
|
||||
modalHTML.style.left = modalHTML.style.right = modalHTML.style.top = modalHTML.style.bottom = ''
|
||||
modalHTML.style.maxHeight = modalHTML.style.height = ''
|
||||
if (typeof element !== 'string') {
|
||||
const el = element as HTMLElement
|
||||
const rect = el.getBoundingClientRect()
|
||||
const rectPopup = modalHTML.getBoundingClientRect()
|
||||
// Vertical
|
||||
if (rect.bottom + rectPopup.height + 28 <= document.body.clientHeight) {
|
||||
modalHTML.style.top = `calc(${rect.bottom}px + 1px)`
|
||||
} else if (rectPopup.height + 28 < rect.top) {
|
||||
modalHTML.style.bottom = `calc(${document.body.clientHeight - rect.y}px + 1px)`
|
||||
} else {
|
||||
modalHTML.style.top = modalHTML.style.bottom = '1rem'
|
||||
}
|
||||
|
||||
// Horizontal
|
||||
if (rect.left + rectPopup.width + 16 > document.body.clientWidth) {
|
||||
modalHTML.style.right = document.body.clientWidth - rect.right + 'px'
|
||||
} else {
|
||||
modalHTML.style.left = rect.left + 'px'
|
||||
}
|
||||
} else if (element === 'right') {
|
||||
modalHTML.style.top = '0'
|
||||
modalHTML.style.bottom = '0'
|
||||
modalHTML.style.right = '0'
|
||||
} else if (element === 'top') {
|
||||
modalHTML.style.top = '15vh'
|
||||
modalHTML.style.left = '50%'
|
||||
modalHTML.style.transform = 'translateX(-50%)'
|
||||
show = true
|
||||
} else if (element === 'account') {
|
||||
modalHTML.style.bottom = '2.75rem'
|
||||
modalHTML.style.left = '5rem'
|
||||
} else if (element === 'full') {
|
||||
modalHTML.style.top = '0'
|
||||
modalHTML.style.bottom = '0'
|
||||
modalHTML.style.left = '0'
|
||||
modalHTML.style.right = '0'
|
||||
}
|
||||
} else {
|
||||
modalHTML.style.top = '50%'
|
||||
modalHTML.style.left = '50%'
|
||||
modalHTML.style.transform = 'translate(-50%, -50%)'
|
||||
show = true
|
||||
}
|
||||
show = fitPopupElement(modalHTML, element)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,12 +109,12 @@
|
||||
}
|
||||
if (value !== null && value !== undefined) dateToEdits()
|
||||
else if (value === null) {
|
||||
edits.map(edit => edit.value = -1)
|
||||
edits.forEach((edit) => { edit.value = -1 })
|
||||
currentDate = today
|
||||
}
|
||||
|
||||
const fixEdits = (): void => {
|
||||
let tempValues: number[] = []
|
||||
const tempValues: number[] = []
|
||||
edits.forEach((edit, i) => {
|
||||
tempValues[i] = edit.value > 0 ? edit.value : getValue(currentDate, edit.id)
|
||||
})
|
||||
@ -139,7 +139,6 @@
|
||||
}
|
||||
|
||||
const keyDown = (ev: KeyboardEvent, ed: TEdits): void => {
|
||||
const target = ev.target as HTMLElement
|
||||
const index = getIndex(ed)
|
||||
|
||||
if (ev.key >= '0' && ev.key <= '9') {
|
||||
@ -175,11 +174,11 @@
|
||||
edits[index].value = -1
|
||||
startTyping = true
|
||||
}
|
||||
if (ev.code === 'ArrowUp' || ev.code === 'ArrowDown' && edits[index].el) {
|
||||
if (ev.code === 'ArrowUp' || (ev.code === 'ArrowDown' && edits[index].el)) {
|
||||
if (edits[index].value !== -1) {
|
||||
let val = (ev.code === 'ArrowUp')
|
||||
? edits[index].value + 1
|
||||
: edits[index].value - 1
|
||||
const val = (ev.code === 'ArrowUp')
|
||||
? edits[index].value + 1
|
||||
: edits[index].value - 1
|
||||
if (currentDate) {
|
||||
currentDate = setValue(val, currentDate, ed)
|
||||
$dpstore.currentDate = currentDate
|
||||
@ -243,7 +242,7 @@
|
||||
let popupComp: HTMLElement
|
||||
$: if (opened && $dpstore.popup) popupComp = $dpstore.popup
|
||||
$: if (opened && edits[0].el && $dpstore.frendlyFocus === undefined) {
|
||||
let frendlyFocus: HTMLElement[] = []
|
||||
const frendlyFocus: HTMLElement[] = []
|
||||
edits.forEach((edit, i) => {
|
||||
if (edit.el) frendlyFocus[i] = edit.el
|
||||
})
|
||||
@ -318,7 +317,7 @@
|
||||
selected = 'day'
|
||||
startTyping = true
|
||||
value = null
|
||||
edits.forEach(edit => edit.value = -1)
|
||||
edits.forEach(edit => { edit.value = -1 })
|
||||
if (edits[0].el) edits[0].el.focus()
|
||||
}}
|
||||
on:blur={(ev) => unfocus(ev, closeBtn)}
|
||||
|
@ -73,8 +73,12 @@ export function isWeekend (date: Date): boolean {
|
||||
}
|
||||
|
||||
export function getMonthName (date: Date, option: 'narrow' | 'short' | 'long' | 'numeric' | '2-digit' = 'long'): string {
|
||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||
return new Intl.DateTimeFormat(locale, { month: option }).format(date)
|
||||
try {
|
||||
const locale = new Intl.NumberFormat().resolvedOptions().locale
|
||||
return new Intl.DateTimeFormat(locale, { month: option }).format(date)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
export type TCellStyle = 'not-selected' | 'selected'
|
||||
|
@ -16,12 +16,26 @@ let currentLocation: string | undefined
|
||||
location.subscribe((loc) => {
|
||||
if (loc.fragment !== currentLocation && loc.fragment !== undefined && loc.fragment.trim().length > 0) {
|
||||
const props = decodeURIComponent(loc.fragment).split('|')
|
||||
showPanel(props[0] as AnyComponent, props[1], props[2], 'full')
|
||||
showPanel(props[0] as AnyComponent, props[1], props[2], (props[3] ?? undefined) as PopupAlignment)
|
||||
} else if ((loc.fragment === undefined || (loc.fragment !== undefined && loc.fragment.trim().length === 0)) && currentLocation !== undefined) {
|
||||
closePanel()
|
||||
}
|
||||
})
|
||||
|
||||
export function getPanelURI (
|
||||
component: AnyComponent,
|
||||
_id: string,
|
||||
_class: string,
|
||||
element?: PopupAlignment,
|
||||
rightSection?: AnyComponent
|
||||
): string {
|
||||
const panelProps = [component, _id, _class]
|
||||
if (typeof element === 'string') {
|
||||
panelProps.push(element)
|
||||
}
|
||||
return encodeURIComponent(panelProps.join('|'))
|
||||
}
|
||||
|
||||
export function showPanel (
|
||||
component: AnyComponent,
|
||||
_id: string,
|
||||
@ -29,7 +43,7 @@ export function showPanel (
|
||||
element?: PopupAlignment,
|
||||
rightSection?: AnyComponent
|
||||
): void {
|
||||
const newLoc = encodeURIComponent([component, _id, _class].join('|'))
|
||||
const newLoc = getPanelURI(component, _id, _class, element)
|
||||
if (currentLocation === newLoc) {
|
||||
return
|
||||
}
|
||||
|
@ -103,3 +103,82 @@ export function closeDatePopup (): void {
|
||||
onChange: undefined
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Place element based on position and underline content element.
|
||||
*
|
||||
* return boolean to show or not modal overlay.
|
||||
*/
|
||||
export function fitPopupElement (modalHTML: HTMLElement, element?: PopupAlignment, contentPanel?: HTMLElement): boolean {
|
||||
let show = true
|
||||
if (element != null) {
|
||||
show = false
|
||||
modalHTML.style.left = modalHTML.style.right = modalHTML.style.top = modalHTML.style.bottom = ''
|
||||
modalHTML.style.maxHeight = modalHTML.style.height = ''
|
||||
if (typeof element !== 'string') {
|
||||
const el = element as HTMLElement
|
||||
const rect = el.getBoundingClientRect()
|
||||
const rectPopup = modalHTML.getBoundingClientRect()
|
||||
// Vertical
|
||||
if (rect.bottom + rectPopup.height + 28 <= document.body.clientHeight) {
|
||||
modalHTML.style.top = `calc(${rect.bottom}px + .75rem)`
|
||||
} else if (rectPopup.height + 28 < rect.top) {
|
||||
modalHTML.style.bottom = `calc(${document.body.clientHeight - rect.y}px + .75rem)`
|
||||
} else {
|
||||
modalHTML.style.top = modalHTML.style.bottom = '1rem'
|
||||
}
|
||||
|
||||
// Horizontal
|
||||
if (rect.left + rectPopup.width + 16 > document.body.clientWidth) {
|
||||
modalHTML.style.right = `${document.body.clientWidth - rect.right}px`
|
||||
} else {
|
||||
modalHTML.style.left = `${rect.left}px`
|
||||
}
|
||||
} else if (element === 'right' && contentPanel !== undefined) {
|
||||
const rect = contentPanel.getBoundingClientRect()
|
||||
modalHTML.style.top = `calc(${rect.top}px + 0.5rem)`
|
||||
modalHTML.style.bottom = '0.75rem'
|
||||
modalHTML.style.right = '0.75rem'
|
||||
show = true
|
||||
} else if (element === 'top') {
|
||||
modalHTML.style.top = '15vh'
|
||||
modalHTML.style.left = '50%'
|
||||
modalHTML.style.transform = 'translateX(-50%)'
|
||||
show = true
|
||||
} else if (element === 'account') {
|
||||
modalHTML.style.bottom = '2.75rem'
|
||||
modalHTML.style.left = '5rem'
|
||||
} else if (element === 'full' && contentPanel !== undefined) {
|
||||
const rect = contentPanel.getBoundingClientRect()
|
||||
modalHTML.style.top = `calc(${rect.top}px + 0.25rem)`
|
||||
modalHTML.style.bottom = '0.25rem'
|
||||
modalHTML.style.left = '0.25rem'
|
||||
modalHTML.style.right = '0.25rem'
|
||||
show = true
|
||||
} else if (element === 'content' && contentPanel !== undefined) {
|
||||
const rect = contentPanel.getBoundingClientRect()
|
||||
modalHTML.style.top = `calc(${rect.top}px)`
|
||||
modalHTML.style.height = `${rect.height}px`
|
||||
modalHTML.style.left = `calc(${rect.left}px)`
|
||||
modalHTML.style.width = `${rect.width}px`
|
||||
} else if (element === 'middle') {
|
||||
if (contentPanel !== undefined) {
|
||||
const rect = contentPanel.getBoundingClientRect()
|
||||
modalHTML.style.top = `calc(${rect.top}px)`
|
||||
} else {
|
||||
modalHTML.style.top = '15%'
|
||||
}
|
||||
modalHTML.style.bottom = '0.75rem'
|
||||
modalHTML.style.left = '50%'
|
||||
modalHTML.style.transform = 'translateX(-50%)'
|
||||
}
|
||||
} else {
|
||||
modalHTML.style.top = '50%'
|
||||
modalHTML.style.left = '50%'
|
||||
modalHTML.style.transform = 'translate(-50%, -50%)'
|
||||
show = true
|
||||
}
|
||||
return show
|
||||
}
|
||||
|
@ -14,11 +14,11 @@
|
||||
//
|
||||
|
||||
// import type { Metadata } from '@anticrm/platform'
|
||||
import { setMetadata } from '@anticrm/platform'
|
||||
import type { Metadata } from '@anticrm/platform'
|
||||
import { setMetadata } from '@anticrm/platform'
|
||||
|
||||
export function setMetadataLocalStorage(id: Metadata<string>, value: string | null): void {
|
||||
if (value) {
|
||||
export function setMetadataLocalStorage (id: Metadata<string>, value: string | null): void {
|
||||
if (value != null) {
|
||||
localStorage.setItem(id, value)
|
||||
} else {
|
||||
localStorage.removeItem(id)
|
||||
@ -26,95 +26,10 @@ export function setMetadataLocalStorage(id: Metadata<string>, value: string | nu
|
||||
setMetadata(id, value)
|
||||
}
|
||||
|
||||
export function fetchMetadataLocalStorage(id: Metadata<string>): string | null {
|
||||
export function fetchMetadataLocalStorage (id: Metadata<string>): string | null {
|
||||
const value = localStorage.getItem(id)
|
||||
if (value !== null) {
|
||||
setMetadata(id, value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// import { Readable, derived, writable } from 'svelte/store'
|
||||
// import { onDestroy, getContext, setContext } from 'svelte'
|
||||
|
||||
// function windowLocation (): Location {
|
||||
// return parseLocation(window.location)
|
||||
// }
|
||||
|
||||
// const locationWritable = writable(windowLocation())
|
||||
// window.addEventListener('popstate', () => {
|
||||
// locationWritable.set(windowLocation())
|
||||
// })
|
||||
|
||||
// const location: Readable<Location> = derived(locationWritable, (loc) => loc)
|
||||
|
||||
// function subscribeLocation (listener: (location: Location) => void, destroyFactory: (op: () => void) => void): void {
|
||||
// const unsubscribe = location.subscribe((location) => {
|
||||
// listener(location)
|
||||
// })
|
||||
// destroyFactory(unsubscribe)
|
||||
// }
|
||||
|
||||
// function navigate (newUrl: string): void {
|
||||
// const curUrl = locationToUrl(windowLocation())
|
||||
// if (curUrl === newUrl) {
|
||||
// return
|
||||
// }
|
||||
// history.pushState(null, '', newUrl)
|
||||
// locationWritable.set(windowLocation())
|
||||
// }
|
||||
|
||||
// function navigateJoin (
|
||||
// path: string[] | undefined,
|
||||
// query: Record<string, string> | undefined,
|
||||
// fragment: string | undefined
|
||||
// ): void {
|
||||
// const newLocation = windowLocation()
|
||||
// if (path != null) {
|
||||
// newLocation.path = path
|
||||
// }
|
||||
// if (query != null) {
|
||||
// // For query we do replace
|
||||
// const currentQuery = newLocation.query || {}
|
||||
// for (const kv of Object.entries(query)) {
|
||||
// currentQuery[kv[0]] = kv[1]
|
||||
// }
|
||||
// }
|
||||
// if (fragment) {
|
||||
// newLocation.fragment = fragment
|
||||
// }
|
||||
// navigate(locationToUrl(newLocation))
|
||||
// }
|
||||
|
||||
// const CONTEXT_ROUTE_VALUE = 'routes.context'
|
||||
|
||||
// export function newRouter<T> (
|
||||
// pattern: string,
|
||||
// matcher: (match: T) => void,
|
||||
// defaults: T | undefined = undefined
|
||||
// ): ApplicationRouter<T> {
|
||||
// const r: Router<any> = getContext(CONTEXT_ROUTE_VALUE)
|
||||
// const navigateOp = (loc: Location): void => {
|
||||
// navigate(locationToUrl(loc))
|
||||
// }
|
||||
// const result = r ? r.newRouter<T>(pattern, defaults) : new Router<T>(pattern, r, defaults, navigateOp)
|
||||
// result.subscribe(matcher)
|
||||
// if (!r) {
|
||||
// // No parent, we need to subscribe for location changes.
|
||||
// subscribeLocation((loc) => {
|
||||
// result.update(loc)
|
||||
// }, onDestroy)
|
||||
// }
|
||||
// if (r) {
|
||||
// // We need to remove child router from parent, if component is destroyed
|
||||
// onDestroy(() => r.clearChildRouter())
|
||||
// }
|
||||
// setContext(CONTEXT_ROUTE_VALUE, result)
|
||||
// return result
|
||||
// }
|
||||
|
||||
// R O U T E R M E T A D A T A K E Y S
|
||||
|
||||
// export function applicationShortcutKey (shortcut: string): Metadata<AnyComponent> {
|
||||
// return ('shortcut:ui.' + shortcut) as Metadata<AnyComponent>
|
||||
// }
|
||||
|
@ -15,24 +15,20 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Card } from '@anticrm/board'
|
||||
import { Icon, showPanel } from '@anticrm/ui'
|
||||
import { getPanelURI, Icon } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import board from '../plugin'
|
||||
|
||||
export let value: Card
|
||||
export let inline: boolean = false
|
||||
|
||||
async function show () {
|
||||
showPanel(board.component.EditCard, value._id, value._class, 'middle')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
href="#{encodeURIComponent([view.component.EditDoc, value._id, value._class].join('|'))}"
|
||||
on:click={show}
|
||||
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon icon={board.icon.Card} size={'small'} />
|
||||
|
@ -15,24 +15,20 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Organization } from '@anticrm/contact'
|
||||
import { showPanel } from '@anticrm/ui'
|
||||
import { getPanelURI } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import Company from './icons/Company.svelte'
|
||||
|
||||
export let value: Organization
|
||||
export let inline: boolean = false
|
||||
|
||||
async function onClick () {
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
href="#{encodeURIComponent([view.component.EditDoc, value._id, value._class].join('|'))}"
|
||||
on:click={onClick}
|
||||
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
|
||||
>
|
||||
<div class="icon circle"><Company size={'small'} /></div>
|
||||
<span class="label">{value.name}</span>
|
||||
|
@ -16,7 +16,7 @@
|
||||
import { formatName, Person } from '@anticrm/contact'
|
||||
import { Hierarchy } from '@anticrm/core'
|
||||
import { Avatar } from '@anticrm/presentation'
|
||||
import { showPanel } from '@anticrm/ui'
|
||||
import { getPanelURI } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let value: Person | undefined
|
||||
@ -25,12 +25,6 @@
|
||||
export let shouldShowPlaceholder = false
|
||||
|
||||
const avatarSize = 'x-small'
|
||||
|
||||
const onClick = async () => {
|
||||
if (value) {
|
||||
showPanel(view.component.EditDoc, value._id, Hierarchy.mixinOrClass(value), 'full')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value || shouldShowPlaceholder}
|
||||
@ -38,8 +32,7 @@
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
href="#{encodeURIComponent([view.component.EditDoc, value._id, Hierarchy.mixinOrClass(value)].join('|'))}"
|
||||
on:click={onClick}
|
||||
href="#{getPanelURI(view.component.EditDoc, value._id, Hierarchy.mixinOrClass(value), 'full')}"
|
||||
>
|
||||
<div class="icon">
|
||||
<Avatar size={avatarSize} avatar={value?.avatar} />
|
||||
|
@ -15,25 +15,19 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Product } from '@anticrm/inventory'
|
||||
import { Icon, showPanel } from '@anticrm/ui'
|
||||
import { getPanelURI, Icon } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import inventory from '../plugin'
|
||||
|
||||
export let value: Product
|
||||
export let inline: boolean = false
|
||||
|
||||
|
||||
function show () {
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
href="#{encodeURIComponent([view.component.EditDoc, value._id, value._class].join('|'))}"
|
||||
on:click={show}
|
||||
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
|
||||
>
|
||||
<div class="icon"><Icon icon={inventory.icon.Products} size={'small'} /></div>
|
||||
<span class="label">{value.name}</span>
|
||||
|
@ -15,24 +15,19 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Lead } from '@anticrm/lead'
|
||||
import { Icon, showPanel } from '@anticrm/ui'
|
||||
import { getPanelURI, Icon } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import lead from '../plugin'
|
||||
|
||||
export let value: Lead
|
||||
export let inline: boolean = false
|
||||
|
||||
async function show () {
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
href="#{encodeURIComponent([view.component.EditDoc, value._id, value._class].join('|'))}"
|
||||
on:click={show}
|
||||
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon icon={lead.icon.Lead} size={'small'} />
|
||||
|
@ -18,7 +18,7 @@
|
||||
import type { Applicant } from '@anticrm/recruit'
|
||||
import recruit from '@anticrm/recruit'
|
||||
import { Icon, Label } from '@anticrm/ui'
|
||||
import { showPanel } from '@anticrm/ui/src/panelup'
|
||||
import { getPanelURI } from '@anticrm/ui/src/panelup'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let value: Applicant
|
||||
@ -26,18 +26,13 @@
|
||||
|
||||
const client = getClient()
|
||||
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
|
||||
|
||||
function show () {
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value && shortLabel}
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
href="#{encodeURIComponent([view.component.EditDoc, value._id, value._class].join('|'))}"
|
||||
on:click={show}
|
||||
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon icon={recruit.icon.Application} size={'small'} />
|
||||
|
@ -15,24 +15,19 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Vacancy } from '@anticrm/recruit'
|
||||
import recruit from '../plugin'
|
||||
import { Icon } from '@anticrm/ui'
|
||||
import { showPanel } from '@anticrm/ui/src/panelup'
|
||||
import { getPanelURI } from '@anticrm/ui/src/panelup'
|
||||
import recruit from '../plugin'
|
||||
|
||||
export let value: Vacancy
|
||||
export let inline: boolean = false
|
||||
|
||||
function show () {
|
||||
showPanel(recruit.component.EditVacancy, value._id, value._class, 'right')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
href="#{encodeURIComponent([recruit.component.EditVacancy, value._id, value._class].join('|'))}"
|
||||
on:click={show}
|
||||
href="#{getPanelURI(recruit.component.EditVacancy, value._id, value._class, 'right')}"
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon icon={recruit.icon.Vacancy} size={'small'} />
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { OrganizationSelector } from '@anticrm/contact-resources'
|
||||
import { Account, Class, Client, Doc, generateId, getCurrentAccount, Ref } from '@anticrm/core'
|
||||
import { getResource, OK, Resource, Severity, Status } from '@anticrm/platform'
|
||||
import { Card, getClient, UserBox } from '@anticrm/presentation'
|
||||
import { Card, getClient, UserBox, UserBoxList } from '@anticrm/presentation'
|
||||
import type { Candidate, Review } from '@anticrm/recruit'
|
||||
import task, { SpaceWithStates } from '@anticrm/task'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
@ -26,6 +26,7 @@
|
||||
import view from '@anticrm/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import recruit from '../../plugin'
|
||||
import calendar from '@anticrm/calendar'
|
||||
|
||||
export let space: Ref<SpaceWithStates>
|
||||
export let candidate: Ref<Person>
|
||||
@ -166,18 +167,43 @@
|
||||
label={recruit.string.Candidate}
|
||||
placeholder={recruit.string.Candidates}
|
||||
bind:value={doc.attachedTo}
|
||||
kind={'link'} size={'x-large'} justify={'left'} width={'100%'} labelDirection={'left'}
|
||||
kind={'link'}
|
||||
size={'x-large'}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
labelDirection={'left'}
|
||||
/>
|
||||
{:else}
|
||||
<div></div>
|
||||
<div />
|
||||
{/if}
|
||||
<EditBox label={recruit.string.Location} icon={recruit.icon.Location} bind:value={location} maxWidth={'13rem'} />
|
||||
<OrganizationSelector
|
||||
bind:value={company} label={recruit.string.Company}
|
||||
kind={'link'} size={'x-large'} justify={'left'} width={'100%'} labelDirection={'left'}
|
||||
bind:value={company}
|
||||
label={recruit.string.Company}
|
||||
kind={'link'}
|
||||
size={'x-large'}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
labelDirection={'left'}
|
||||
/>
|
||||
<DateRangePicker title={recruit.string.StartDate} bind:value={startDate} withTime on:change={updateStart} />
|
||||
<DateRangePicker title={recruit.string.DueDate} bind:value={dueDate} withTime />
|
||||
|
||||
<Row>
|
||||
<UserBoxList
|
||||
_class={contact.class.Employee}
|
||||
items={doc.participants}
|
||||
title={calendar.string.Participants}
|
||||
on:open={(evt) => {
|
||||
doc.participants = [...(doc.participants ?? []), evt.detail._id]
|
||||
}}
|
||||
on:delete={(evt) => {
|
||||
doc.participants = doc.participants?.filter((it) => it !== evt.detail._id) ?? [currentUser.employee]
|
||||
}}
|
||||
noItems={calendar.string.NoParticipants}
|
||||
/>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<StyledTextBox
|
||||
emphasized
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import type { Review } from '@anticrm/recruit'
|
||||
import recruit from '@anticrm/recruit'
|
||||
import { closeTooltip, Icon, showPanel } from '@anticrm/ui'
|
||||
import { getPanelURI, Icon } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let value: Review
|
||||
@ -34,19 +34,13 @@
|
||||
shortLabel = r
|
||||
})
|
||||
}
|
||||
|
||||
function show () {
|
||||
closeTooltip()
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'right')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value && shortLabel}
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
href="#{encodeURIComponent([view.component.EditDoc, value._id, value._class].join('|'))}"
|
||||
on:click={show}
|
||||
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'right')}"
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon icon={recruit.icon.Application} size={'small'} />
|
||||
|
@ -16,7 +16,7 @@
|
||||
<script lang="ts">
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import type { Issue } from '@anticrm/task'
|
||||
import { Icon, Label, showPanel } from '@anticrm/ui'
|
||||
import { getPanelURI, Icon, Label } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import task from '../plugin'
|
||||
|
||||
@ -25,18 +25,13 @@
|
||||
|
||||
const client = getClient()
|
||||
const shortLabel = client.getHierarchy().getClass(value._class).shortLabel
|
||||
|
||||
function show () {
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if value && shortLabel}
|
||||
<a
|
||||
class="flex-presenter"
|
||||
class:inline-presenter={inline}
|
||||
href="#{encodeURIComponent([view.component.EditDoc, value._id, value._class].join('|'))}"
|
||||
on:click={show}
|
||||
href="#{getPanelURI(view.component.EditDoc, value._id, value._class, 'full')}"
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon icon={task.icon.Task} size={'small'} />
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
<script lang="ts">
|
||||
import contact from '@anticrm/contact'
|
||||
import { FindOptions, Ref, WithLookup } from '@anticrm/core'
|
||||
@ -58,7 +57,7 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
let issue: Issue
|
||||
|
||||
function toIssue (object:any): WithLookup<Issue> {
|
||||
function toIssue (object: any): WithLookup<Issue> {
|
||||
return object as WithLookup<Issue>
|
||||
}
|
||||
|
||||
@ -70,6 +69,9 @@
|
||||
</script>
|
||||
|
||||
{#if currentTeam}
|
||||
<div class="flex-between label font-medium w-full p-4">
|
||||
Board
|
||||
</div>
|
||||
<Kanban
|
||||
_class={tracker.class.Issue}
|
||||
space={currentSpace}
|
||||
@ -81,63 +83,67 @@
|
||||
rankFieldName={'rank'}
|
||||
>
|
||||
<svelte:fragment slot="header" let:state let:count>
|
||||
<div class="header flex">
|
||||
<div class="flex-between label">
|
||||
<div class="header flex-col">
|
||||
<div class="flex-between label font-medium w-full h-full mb-4">
|
||||
<div class="flex-row-center gap-2">
|
||||
<Icon icon={state.icon} size={'small'} />
|
||||
<span class="lines-limit-2">{state.title}</span>
|
||||
<span class="counter ml-2">{count}</span>
|
||||
<span class="lines-limit-2 ml-2">{state.title}</span>
|
||||
<span class="counter ml-2 text-md">{count}</span>
|
||||
</div>
|
||||
<div class='flex gap-1'>
|
||||
<div class="flex gap-1">
|
||||
<Tooltip label={tracker.string.AddIssueTooltip} direction={'left'}>
|
||||
<Button icon={IconAdd} kind={'transparent'} on:click={(evt) => {
|
||||
showPopup(CreateIssue, { space: currentSpace, issueStatus: state._id }, evt.target)
|
||||
}}/>
|
||||
</Tooltip>
|
||||
<Button icon={IconMoreH} kind={'transparent'}/>
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind={'transparent'}
|
||||
on:click={(evt) => {
|
||||
showPopup(CreateIssue, { space: currentSpace, issueStatus: state._id }, evt.target)
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button icon={IconMoreH} kind={'transparent'} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="card" let:object>
|
||||
{@const issue = toIssue(object) }
|
||||
<div class='flex-row h-18'>
|
||||
<div class='flex-between mb-2'>
|
||||
<IssuePresenter value={object} {currentTeam}/>
|
||||
{#if issue.$lookup?.assignee }
|
||||
<Component is={view.component.ObjectPresenter} props={{ value: issue.$lookup.assignee, props: { shouldShowName: false } }}/>
|
||||
{@const issue = toIssue(object)}
|
||||
<div class="flex-row h-18">
|
||||
<div class="flex-between mb-2">
|
||||
<IssuePresenter value={object} {currentTeam} />
|
||||
{#if issue.$lookup?.assignee}
|
||||
<Component
|
||||
is={view.component.ObjectPresenter}
|
||||
props={{ value: issue.$lookup.assignee, props: { showLabel: false } }}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<span class='fs-bold title'>
|
||||
<span class="fs-bold title">
|
||||
{object.title}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Kanban>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 3rem;
|
||||
min-height: 3rem;
|
||||
height: 6rem;
|
||||
min-height: 6rem;
|
||||
user-select: none;
|
||||
|
||||
.filter {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-bottom: 0.5rem;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
color: var(--theme-caption-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
.counter {
|
||||
color: rgba(var(--theme-caption-color), 0.8) !important;
|
||||
font-weight: unset;
|
||||
color: rgba(var(--theme-caption-color), 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
.title {
|
||||
color: var(--theme-caption-color)
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user