Unify Popup and Panel behaviour (#1347)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-04-09 23:00:48 +07:00 committed by GitHub
parent 9b9c785da5
commit 662cca9282
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 245 additions and 354 deletions

View File

@ -238,7 +238,7 @@
}
.kanban-content {
display: flex;
margin: 1.5rem 2rem;
margin: 0.5rem 2rem;
height: 100%;
}

View File

@ -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

View File

@ -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;

View File

@ -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; }

View File

@ -39,7 +39,7 @@
.ad-tools {
position: absolute;
display: flex;
top: 1rem;
top: 1.375rem;
right: 2rem;
&.grow-reverse {
left: 0px;

View File

@ -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)
}
}
@ -141,14 +81,15 @@
{#if !component}
<Spinner />
{:else}
<div class="antiPanel panel-instance" bind:this={modalHTML}>
<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 }
position={props.element}
on:close={_close}
on:update={() => {
if (props) {
@ -157,8 +98,9 @@
}}
/>
</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;

View File

@ -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)
}
}

View File

@ -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,9 +174,9 @@
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')
const val = (ev.code === 'ArrowUp')
? edits[index].value + 1
: edits[index].value - 1
if (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)}

View File

@ -73,8 +73,12 @@ export function isWeekend (date: Date): boolean {
}
export function getMonthName (date: Date, option: 'narrow' | 'short' | 'long' | 'numeric' | '2-digit' = 'long'): string {
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'

View File

@ -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
}

View File

@ -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
}

View File

@ -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>
// }

View File

@ -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'} />

View File

@ -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>

View File

@ -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} />

View File

@ -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>

View File

@ -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'} />

View File

@ -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'} />

View File

@ -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'} />

View File

@ -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

View File

@ -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'} />

View File

@ -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'} />

View File

@ -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) => {
<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={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>