mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-15 12:55:59 +00:00
Rework panel navigation (#928)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
f2402c3208
commit
db52006a9d
@ -14,17 +14,15 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { AttachedData, Doc, Ref } from '@anticrm/core'
|
||||
import type { IntlString, Asset } from '@anticrm/platform'
|
||||
import type { Channel, ChannelProvider } from '@anticrm/contact'
|
||||
import { getClient } from '..'
|
||||
|
||||
import type { AttachedData, Doc, Ref } from '@anticrm/core'
|
||||
import type { Asset, IntlString } from '@anticrm/platform'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import { Tooltip, CircleButton } from '@anticrm/ui'
|
||||
import ChannelsPopup from './ChannelsPopup.svelte'
|
||||
|
||||
import contact from '@anticrm/contact'
|
||||
import { CircleButton, Tooltip } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getClient } from '..'
|
||||
import { getChannelProviders } from '../utils'
|
||||
import ChannelsPopup from './ChannelsPopup.svelte'
|
||||
|
||||
export let value: AttachedData<Channel>[] | AttachedData<Channel> | null
|
||||
export let size: 'small' | 'medium' | 'large' | 'x-large' = 'large'
|
||||
@ -42,15 +40,6 @@
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function getProviders (): Promise<Map<Ref<ChannelProvider>, ChannelProvider>> {
|
||||
const providers = await client.findAll(contact.class.ChannelProvider, {})
|
||||
const map = new Map<Ref<ChannelProvider>, ChannelProvider>()
|
||||
for (const provider of providers) {
|
||||
map.set(provider._id, provider)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
function getProvider (item: AttachedData<Channel>, map: Map<Ref<ChannelProvider>, ChannelProvider>): any | undefined {
|
||||
const provider = map.get(item.provider)
|
||||
if (provider) {
|
||||
@ -68,7 +57,7 @@
|
||||
|
||||
async function update (value: AttachedData<Channel>[] | AttachedData<Channel>) {
|
||||
const result = []
|
||||
const map = await getProviders()
|
||||
const map = await getChannelProviders()
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
const provider = getProvider(item, map)
|
||||
|
@ -22,9 +22,11 @@ import login from '@anticrm/login'
|
||||
import { getMetadata } from '@anticrm/platform'
|
||||
import { LiveQuery as LQ } from '@anticrm/query'
|
||||
import { onDestroy } from 'svelte'
|
||||
import contact, { ChannelProvider } from '@anticrm/contact'
|
||||
|
||||
let liveQuery: LQ
|
||||
let client: TxOperations
|
||||
let channelProviders: Promise<ChannelProvider[]> | undefined
|
||||
|
||||
class UIClient extends TxOperations implements Client {
|
||||
constructor (client: Client, private readonly liveQuery: LQ) {
|
||||
@ -47,6 +49,7 @@ export function setClient (_client: Client): void {
|
||||
_client.notify = (tx: Tx) => {
|
||||
liveQuery.tx(tx).catch((err) => console.log(err))
|
||||
}
|
||||
channelProviders = client.findAll(contact.class.ChannelProvider, {})
|
||||
}
|
||||
|
||||
export class LiveQuery {
|
||||
@ -110,3 +113,12 @@ export function getAttributePresenterClass (attribute: AnyAttribute): Ref<Class<
|
||||
}
|
||||
return attrClass
|
||||
}
|
||||
|
||||
export async function getChannelProviders (): Promise<Map<Ref<ChannelProvider>, ChannelProvider>> {
|
||||
const cp = (await channelProviders) ?? []
|
||||
const map = new Map<Ref<ChannelProvider>, ChannelProvider>()
|
||||
for (const provider of cp) {
|
||||
map.set(provider._id, provider)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
146
packages/ui/src/components/PanelInstance.svelte
Normal file
146
packages/ui/src/components/PanelInstance.svelte
Normal file
@ -0,0 +1,146 @@
|
||||
<!--
|
||||
// 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">
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { afterUpdate } from 'svelte'
|
||||
import { Spinner } from '..'
|
||||
import { closePanel, PanelProps, panelstore as modal } from '../panelup'
|
||||
|
||||
let modalHTML: HTMLElement
|
||||
let componentInstance: any
|
||||
let show: boolean = false
|
||||
|
||||
let props: PanelProps | undefined
|
||||
function _close () {
|
||||
closePanel()
|
||||
}
|
||||
|
||||
$: props = $modal.panel
|
||||
|
||||
function escapeClose () {
|
||||
if (componentInstance && componentInstance.canClose) {
|
||||
if (!componentInstance.canClose()) return
|
||||
}
|
||||
_close()
|
||||
}
|
||||
|
||||
const fitPopup = (props: PanelProps): 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') {
|
||||
modalHTML.style.top = '0'
|
||||
modalHTML.style.bottom = '0'
|
||||
modalHTML.style.right = '0'
|
||||
} else if (props.element === 'float') {
|
||||
modalHTML.style.top = '4rem'
|
||||
modalHTML.style.bottom = '4rem'
|
||||
modalHTML.style.right = '4rem'
|
||||
} else if (props.element === 'account') {
|
||||
modalHTML.style.bottom = '2.75rem'
|
||||
modalHTML.style.left = '5rem'
|
||||
} else if (props.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown (ev: KeyboardEvent) {
|
||||
if (ev.key === 'Escape') {
|
||||
escapeClose()
|
||||
}
|
||||
}
|
||||
afterUpdate(() => {
|
||||
if (props) fitPopup(props)
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:resize={() => {
|
||||
if (props) fitPopup(props)
|
||||
}}
|
||||
on:keydown={(evt) => {
|
||||
if (props) handleKeydown(evt)
|
||||
}}
|
||||
/>
|
||||
{#if props}
|
||||
{#await getResource(props.component)}
|
||||
<Spinner />
|
||||
{:then component}
|
||||
<div class="popup" bind:this={modalHTML} style={'z-index: 401'}>
|
||||
<svelte:component
|
||||
this={component}
|
||||
bind:this={componentInstance}
|
||||
_id={props._id}
|
||||
_class={props._class}
|
||||
on:update={fitPopup}
|
||||
on:close={_close}
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-overlay" class:show style={'z-index: 400'} on:click={() => escapeClose()} />
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.popup {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
max-height: calc(100vh - 2rem);
|
||||
background-color: transparent;
|
||||
}
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
&.show {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -14,10 +14,9 @@
|
||||
//
|
||||
|
||||
import { SvelteComponent } from 'svelte'
|
||||
import type { AnySvelteComponent, AnyComponent, PopupAlignment, LabelAndProps, TooltipAligment } from './types'
|
||||
import { getResource, IntlString, addStringsLoader } from '@anticrm/platform'
|
||||
import { addStringsLoader } from '@anticrm/platform'
|
||||
import { uiId } from './plugin'
|
||||
import { writable, readable } from 'svelte/store'
|
||||
import { readable } from 'svelte/store'
|
||||
|
||||
import Root from './components/internal/Root.svelte'
|
||||
|
||||
@ -90,99 +89,17 @@ export { default as IconInfo } from './components/icons/Info.svelte'
|
||||
export { default as IconBlueCheck } from './components/icons/BlueCheck.svelte'
|
||||
export { default as IconArrowLeft } from './components/icons/ArrowLeft.svelte'
|
||||
|
||||
export { default as PanelInstance } from './components/PanelInstance.svelte'
|
||||
|
||||
export * from './utils'
|
||||
export * from './popups'
|
||||
export * from './tooltips'
|
||||
export * from './panelup'
|
||||
|
||||
export function createApp (target: HTMLElement): SvelteComponent {
|
||||
return new Root({ target })
|
||||
}
|
||||
|
||||
interface CompAndProps {
|
||||
id: string
|
||||
is: AnySvelteComponent
|
||||
props: any
|
||||
element?: PopupAlignment
|
||||
onClose?: (result: any) => void
|
||||
close: () => void
|
||||
}
|
||||
|
||||
export const popupstore = writable<CompAndProps[]>([])
|
||||
|
||||
function addPopup (props: CompAndProps): void {
|
||||
popupstore.update((popups) => {
|
||||
popups.push(props)
|
||||
return popups
|
||||
})
|
||||
}
|
||||
let popupId: number = 0
|
||||
export function showPopup (
|
||||
component: AnySvelteComponent | AnyComponent,
|
||||
props: any,
|
||||
element?: PopupAlignment,
|
||||
onClose?: (result: any) => void
|
||||
): () => void {
|
||||
const id = `${popupId++}`
|
||||
const closePopupOp = (): void => {
|
||||
popupstore.update((popups) => {
|
||||
const pos = popups.findIndex(p => p.id === id)
|
||||
if (pos !== -1) {
|
||||
popups.splice(pos, 1)
|
||||
}
|
||||
return popups
|
||||
})
|
||||
}
|
||||
if (typeof component === 'string') {
|
||||
getResource(component).then((resolved) => addPopup({ id, is: resolved, props, element, onClose, close: closePopupOp })).catch((err) => console.log(err))
|
||||
} else {
|
||||
addPopup({ id, is: component, props, element, onClose, close: closePopupOp })
|
||||
}
|
||||
return closePopupOp
|
||||
}
|
||||
|
||||
export function closePopup (): void {
|
||||
popupstore.update((popups) => {
|
||||
popups.pop()
|
||||
return popups
|
||||
})
|
||||
}
|
||||
|
||||
export const tooltipstore = writable<LabelAndProps>({
|
||||
label: undefined,
|
||||
element: undefined,
|
||||
direction: undefined,
|
||||
component: undefined,
|
||||
props: undefined,
|
||||
anchor: undefined
|
||||
})
|
||||
|
||||
export function showTooltip (
|
||||
label: IntlString | undefined,
|
||||
element: HTMLElement,
|
||||
direction?: TooltipAligment,
|
||||
component?: AnySvelteComponent | AnyComponent,
|
||||
props?: any,
|
||||
anchor?: HTMLElement
|
||||
): void {
|
||||
tooltipstore.set({
|
||||
label: label,
|
||||
element: element,
|
||||
direction: direction,
|
||||
component: component,
|
||||
props: props,
|
||||
anchor: anchor
|
||||
})
|
||||
}
|
||||
|
||||
export function closeTooltip (): void {
|
||||
tooltipstore.set({
|
||||
label: undefined,
|
||||
element: undefined,
|
||||
direction: undefined,
|
||||
component: undefined,
|
||||
props: undefined,
|
||||
anchor: undefined
|
||||
})
|
||||
}
|
||||
|
||||
export const ticker = readable(Date.now(), (set) => {
|
||||
setInterval(() => {
|
||||
set(Date.now())
|
||||
|
51
packages/ui/src/panelup.ts
Normal file
51
packages/ui/src/panelup.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { writable } from 'svelte/store'
|
||||
import { getCurrentLocation, location, navigate } from './location'
|
||||
import { AnyComponent, PopupAlignment } from './types'
|
||||
|
||||
export interface PanelProps {
|
||||
component: AnyComponent
|
||||
_id: string
|
||||
_class: string
|
||||
element?: PopupAlignment
|
||||
}
|
||||
|
||||
export const panelstore = writable < {panel?: PanelProps|undefined}>({ panel: undefined })
|
||||
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')
|
||||
}
|
||||
})
|
||||
|
||||
export function showPanel (
|
||||
component: AnyComponent,
|
||||
_id: string,
|
||||
_class: string,
|
||||
element?: PopupAlignment
|
||||
): void {
|
||||
const newLoc = encodeURIComponent([component, _id, _class].join('|'))
|
||||
if (currentLocation === newLoc) {
|
||||
return
|
||||
}
|
||||
currentLocation = newLoc
|
||||
panelstore.update(() => {
|
||||
return { panel: { component, _id, _class, element } }
|
||||
})
|
||||
const location = getCurrentLocation()
|
||||
if (location.fragment !== currentLocation) {
|
||||
location.fragment = currentLocation
|
||||
navigate(location)
|
||||
}
|
||||
}
|
||||
|
||||
export function closePanel (): void {
|
||||
panelstore.update(() => {
|
||||
return { panel: undefined }
|
||||
})
|
||||
const location = getCurrentLocation()
|
||||
location.fragment = undefined
|
||||
currentLocation = undefined
|
||||
navigate(location)
|
||||
}
|
51
packages/ui/src/popups.ts
Normal file
51
packages/ui/src/popups.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { AnySvelteComponent, AnyComponent, PopupAlignment } from './types'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
interface CompAndProps {
|
||||
id: string
|
||||
is: AnySvelteComponent
|
||||
props: any
|
||||
element?: PopupAlignment
|
||||
onClose?: (result: any) => void
|
||||
close: () => void
|
||||
}
|
||||
|
||||
export const popupstore = writable<CompAndProps[]>([])
|
||||
function addPopup (props: CompAndProps): void {
|
||||
popupstore.update((popups) => {
|
||||
popups.push(props)
|
||||
return popups
|
||||
})
|
||||
}
|
||||
let popupId: number = 0
|
||||
export function showPopup (
|
||||
component: AnySvelteComponent | AnyComponent,
|
||||
props: any,
|
||||
element?: PopupAlignment,
|
||||
onClose?: (result: any) => void
|
||||
): () => void {
|
||||
const id = `${popupId++}`
|
||||
const closePopupOp = (): void => {
|
||||
popupstore.update((popups) => {
|
||||
const pos = popups.findIndex(p => p.id === id)
|
||||
if (pos !== -1) {
|
||||
popups.splice(pos, 1)
|
||||
}
|
||||
return popups
|
||||
})
|
||||
}
|
||||
if (typeof component === 'string') {
|
||||
getResource(component).then((resolved) => addPopup({ id, is: resolved, props, element, onClose, close: closePopupOp })).catch((err) => console.log(err))
|
||||
} else {
|
||||
addPopup({ id, is: component, props, element, onClose, close: closePopupOp })
|
||||
}
|
||||
return closePopupOp
|
||||
}
|
||||
|
||||
export function closePopup (): void {
|
||||
popupstore.update((popups) => {
|
||||
popups.pop()
|
||||
return popups
|
||||
})
|
||||
}
|
41
packages/ui/src/tooltips.ts
Normal file
41
packages/ui/src/tooltips.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { AnySvelteComponent, AnyComponent, LabelAndProps, TooltipAligment } from './types'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
export const tooltipstore = writable<LabelAndProps>({
|
||||
label: undefined,
|
||||
element: undefined,
|
||||
direction: undefined,
|
||||
component: undefined,
|
||||
props: undefined,
|
||||
anchor: undefined
|
||||
})
|
||||
|
||||
export function showTooltip (
|
||||
label: IntlString | undefined,
|
||||
element: HTMLElement,
|
||||
direction?: TooltipAligment,
|
||||
component?: AnySvelteComponent | AnyComponent,
|
||||
props?: any,
|
||||
anchor?: HTMLElement
|
||||
): void {
|
||||
tooltipstore.set({
|
||||
label: label,
|
||||
element: element,
|
||||
direction: direction,
|
||||
component: component,
|
||||
props: props,
|
||||
anchor: anchor
|
||||
})
|
||||
}
|
||||
|
||||
export function closeTooltip (): void {
|
||||
tooltipstore.set({
|
||||
label: undefined,
|
||||
element: undefined,
|
||||
direction: undefined,
|
||||
component: undefined,
|
||||
props: undefined,
|
||||
anchor: undefined
|
||||
})
|
||||
}
|
@ -15,15 +15,15 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Organization } from '@anticrm/contact'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { showPanel } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import Company from './icons/Company.svelte'
|
||||
import { EditDoc } from '@anticrm/view-resources'
|
||||
|
||||
export let value: Organization
|
||||
export let inline: boolean = false
|
||||
|
||||
async function onClick () {
|
||||
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -16,14 +16,14 @@
|
||||
<script lang="ts">
|
||||
import { formatName, Person } from '@anticrm/contact'
|
||||
import { Avatar } from '@anticrm/presentation'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { EditDoc } from '@anticrm/view-resources'
|
||||
import { showPanel } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let value: Person
|
||||
export let inline: boolean = false
|
||||
|
||||
async function onClick () {
|
||||
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -15,16 +15,16 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Product } from '@anticrm/inventory'
|
||||
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
|
||||
import { closeTooltip, Icon, showPanel } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import inventory from '../plugin'
|
||||
import { EditDoc } from '@anticrm/view-resources'
|
||||
|
||||
export let value: Product
|
||||
export let inline: boolean = false
|
||||
|
||||
function show () {
|
||||
closeTooltip()
|
||||
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -19,8 +19,9 @@
|
||||
import { ContactPresenter } from '@anticrm/contact-resources'
|
||||
import type { WithLookup } from '@anticrm/core'
|
||||
import type { Lead } from '@anticrm/lead'
|
||||
import { ActionIcon, IconMoreH, showPopup } from '@anticrm/ui'
|
||||
import { ContextMenu, EditDoc } from '@anticrm/view-resources'
|
||||
import { ActionIcon, IconMoreH, showPanel, showPopup } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { ContextMenu } from '@anticrm/view-resources'
|
||||
import lead from '../plugin'
|
||||
|
||||
export let object: WithLookup<Lead>
|
||||
@ -31,7 +32,7 @@
|
||||
}
|
||||
|
||||
function showLead () {
|
||||
showPopup(EditDoc, { _id: object._id, _class: object._class }, 'full')
|
||||
showPanel(view.component.EditDoc, object._id, object._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -15,15 +15,15 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Lead } from '@anticrm/lead'
|
||||
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
|
||||
import { closeTooltip, Icon, showPanel } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import lead from '../plugin'
|
||||
import { EditDoc } from '@anticrm/view-resources'
|
||||
|
||||
export let value: Lead
|
||||
|
||||
function show () {
|
||||
closeTooltip()
|
||||
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -14,11 +14,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Applicant } from '@anticrm/recruit'
|
||||
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { EditDoc } from '@anticrm/view-resources'
|
||||
import type { Applicant } from '@anticrm/recruit'
|
||||
import recruit from '@anticrm/recruit'
|
||||
import { closeTooltip, Icon } from '@anticrm/ui'
|
||||
import { showPanel } from '@anticrm/ui/src/panelup'
|
||||
import view from '@anticrm/view'
|
||||
|
||||
export let value: Applicant
|
||||
|
||||
@ -27,7 +28,7 @@
|
||||
|
||||
function show () {
|
||||
closeTooltip()
|
||||
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -13,22 +13,21 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Avatar } from '@anticrm/presentation'
|
||||
import { showPopup, ActionIcon, IconMoreH } from '@anticrm/ui'
|
||||
import type { WithLookup } from '@anticrm/core'
|
||||
import type { Applicant } from '@anticrm/recruit'
|
||||
|
||||
import { CommentsPresenter } from '@anticrm/chunter-resources'
|
||||
import { AttachmentsPresenter } from '@anticrm/attachment-resources'
|
||||
import { CommentsPresenter } from '@anticrm/chunter-resources'
|
||||
import { formatName } from '@anticrm/contact'
|
||||
import type { WithLookup } from '@anticrm/core'
|
||||
import { Avatar } from '@anticrm/presentation'
|
||||
import type { Applicant } from '@anticrm/recruit'
|
||||
import { ActionIcon, IconMoreH, showPanel } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import ApplicationPresenter from './ApplicationPresenter.svelte'
|
||||
import { EditDoc } from '@anticrm/view-resources'
|
||||
|
||||
export let object: WithLookup<Applicant>
|
||||
export let draggable: boolean
|
||||
|
||||
function showCandidate () {
|
||||
showPopup(EditDoc, { _id: object.attachedTo, _class: object.attachedToClass }, 'full')
|
||||
showPanel(view.component.EditDoc, object.attachedTo, object.attachedToClass, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -14,11 +14,11 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Issue } from '@anticrm/task'
|
||||
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import type { Issue } from '@anticrm/task'
|
||||
import { closeTooltip, Icon, showPanel } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import task from '../plugin'
|
||||
import { EditDoc } from '@anticrm/view-resources'
|
||||
|
||||
export let value: Issue
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
|
||||
function show () {
|
||||
closeTooltip()
|
||||
showPopup(EditDoc, { _id: value._id, _class: value._class }, 'full')
|
||||
showPanel(view.component.EditDoc, value._id, value._class, 'full')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -71,6 +71,7 @@ export default async (): Promise<Resources> => ({
|
||||
DateEditor,
|
||||
DatePresenter,
|
||||
RolePresenter,
|
||||
ObjectPresenter
|
||||
ObjectPresenter,
|
||||
EditDoc
|
||||
}
|
||||
})
|
||||
|
@ -15,7 +15,6 @@
|
||||
//
|
||||
|
||||
import { IntlString, mergeIds } from '@anticrm/platform'
|
||||
|
||||
import view, { viewId } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(viewId, view, {
|
||||
|
@ -171,7 +171,8 @@ const view = plugin(viewId, {
|
||||
Table: '' as Ref<ViewletDescriptor>
|
||||
},
|
||||
component: {
|
||||
ObjectPresenter: '' as AnyComponent
|
||||
ObjectPresenter: '' as AnyComponent,
|
||||
EditDoc: '' as AnyComponent
|
||||
},
|
||||
icon: {
|
||||
Table: '' as Asset,
|
||||
|
@ -15,15 +15,15 @@
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import type { Ref } from '@anticrm/core';
|
||||
import { getCurrentLocation,navigate } from '@anticrm/ui';
|
||||
import type { Application } from '@anticrm/workbench';
|
||||
import AppItem from './AppItem.svelte';
|
||||
import type { Ref } from '@anticrm/core'
|
||||
import { getCurrentLocation, navigate } from '@anticrm/ui'
|
||||
import type { Application } from '@anticrm/workbench'
|
||||
import AppItem from './AppItem.svelte'
|
||||
|
||||
export let active: Ref<Application> | undefined
|
||||
export let apps: Application[] = []
|
||||
|
||||
function navigateApp(app: Ref<Application>) {
|
||||
function navigateApp (app: Ref<Application>) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = app
|
||||
loc.path.length = 2
|
||||
@ -34,6 +34,6 @@
|
||||
|
||||
<div class="flex-col">
|
||||
{#each apps as app}
|
||||
<AppItem selected={app._id === active} icon={app.icon} label={app.label} action={async () => {navigateApp(app._id)}}/>
|
||||
<AppItem selected={app._id === active} icon={app.icon} label={app.label} action={async () => { navigateApp(app._id) }} notify={false}/>
|
||||
{/each}
|
||||
</div>
|
||||
|
@ -25,7 +25,8 @@
|
||||
navigate,
|
||||
Popup,
|
||||
showPopup,
|
||||
TooltipInstance
|
||||
TooltipInstance,
|
||||
PanelInstance
|
||||
} from '@anticrm/ui'
|
||||
import type { Application, NavigatorModel, ViewConfiguration } from '@anticrm/workbench'
|
||||
import { onDestroy } from 'svelte'
|
||||
@ -234,6 +235,7 @@
|
||||
</div>
|
||||
<!-- <div class="aside"><Chat thread/></div> -->
|
||||
</div>
|
||||
<PanelInstance/>
|
||||
<Popup />
|
||||
<TooltipInstance />
|
||||
{:else}
|
||||
|
@ -15,14 +15,12 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { IntlString, Asset } from '@anticrm/platform'
|
||||
import type { Ref, Doc, Class, Space } from '@anticrm/core'
|
||||
import { IconClose, Label, EditBox, ToggleWithLabel, Grid, Icon } from '@anticrm/ui'
|
||||
import { getClient, createQuery, AttributeBarEditor } from '@anticrm/presentation'
|
||||
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import type { Class, Ref, Space } from '@anticrm/core'
|
||||
import core from '@anticrm/core'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { EditBox, Grid, Icon, IconClose, Label, ToggleWithLabel } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let _id: Ref<Space>
|
||||
export let spaceClass: Ref<Class<Space>>
|
||||
@ -43,7 +41,7 @@
|
||||
const tabs: IntlString[] = ['General' as IntlString, 'Members' as IntlString]
|
||||
let selected = 0
|
||||
|
||||
function onNameChange(ev: Event) {
|
||||
function onNameChange (ev: Event) {
|
||||
client.updateDoc(spaceClass, space.space, space._id, { name: (ev.target as HTMLInputElement).value })
|
||||
}
|
||||
|
||||
|
@ -71,20 +71,18 @@
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<TreeNode label={model.label} actions={[addSpace]}>
|
||||
<TreeNode label={model.label} actions={async () => [addSpace]}>
|
||||
{#each spaces as space}
|
||||
{#await getActions(space) then actions}
|
||||
<TreeItem
|
||||
_id={space._id}
|
||||
title={space.name}
|
||||
icon={classIcon(client, space._class)}
|
||||
selected={currentSpace === space._id}
|
||||
{actions}
|
||||
on:click={() => {
|
||||
selectSpace(space._id)
|
||||
}}
|
||||
/>
|
||||
{/await}
|
||||
<TreeItem
|
||||
_id={space._id}
|
||||
title={space.name}
|
||||
icon={classIcon(client, space._class)}
|
||||
selected={currentSpace === space._id}
|
||||
actions={() => getActions(space)}
|
||||
on:click={() => {
|
||||
selectSpace(space._id)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</TreeNode>
|
||||
</div>
|
||||
|
@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import Collapsed from '../icons/Collapsed.svelte'
|
||||
import Expanded from '../icons/Expanded.svelte'
|
||||
@ -31,18 +30,23 @@
|
||||
export let node = false
|
||||
export let collapsed = false
|
||||
export let selected = false
|
||||
export let actions: Action[] = []
|
||||
export let actions: () => Promise<Action[]> = async () => []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let hovered = false
|
||||
function onMenuClick(ev: MouseEvent) {
|
||||
async function onMenuClick (ev: MouseEvent) {
|
||||
showPopup(Menu, { actions: await actions(), ctx: _id }, ev.target as HTMLElement, () => {
|
||||
hovered = false
|
||||
})
|
||||
hovered = true
|
||||
showPopup(Menu, { actions, ctx: _id }, ev.target as HTMLElement, () => { hovered = false })
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container" class:selected class:hovered
|
||||
<div
|
||||
class="container"
|
||||
class:selected
|
||||
class:hovered
|
||||
on:click|stopPropagation={() => {
|
||||
if (node && !icon) collapsed = !collapsed
|
||||
dispatch('click')
|
||||
@ -50,29 +54,42 @@
|
||||
>
|
||||
<div class="icon" class:sub={!node}>
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'}/>
|
||||
{:else}
|
||||
{#if collapsed}<Collapsed size={'small'} />{:else}<Expanded size={'small'} />{/if}
|
||||
{/if}
|
||||
<Icon {icon} size={'small'} />
|
||||
{:else if collapsed}<Collapsed size={'small'} />{:else}<Expanded size={'small'} />{/if}
|
||||
</div>
|
||||
<span class="label" class:sub={node}>
|
||||
{#if label}<Label {label}/>{:else}{title}{/if}
|
||||
{#if label}<Label {label} />{:else}{title}{/if}
|
||||
</span>
|
||||
{#if actions.length === 1}
|
||||
<div class="tool">
|
||||
<ActionIcon label={actions[0].label} icon={actions[0].icon} size={'small'} action={(ev) => { actions[0].action(_id, ev) }} />
|
||||
</div>
|
||||
{:else if actions.length > 1}
|
||||
{#if node === false}
|
||||
<div class="tool" on:click|stopPropagation={onMenuClick}>
|
||||
<IconMoreV size={'small'} />
|
||||
</div>
|
||||
{:else}
|
||||
{#await actions() then actionItems}
|
||||
{#if actionItems.length === 1}
|
||||
<div class="tool">
|
||||
<ActionIcon
|
||||
label={actionItems[0].label}
|
||||
icon={actionItems[0].icon}
|
||||
size={'small'}
|
||||
action={(ev) => {
|
||||
actionItems[0].action(_id, ev)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else if actionItems.length > 1}
|
||||
<div class="tool" on:click|stopPropagation={onMenuClick}>
|
||||
<IconMoreV size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
{#if notifications > 0 && collapsed}
|
||||
<div class="counter">{notifications}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if node && !icon && !collapsed}
|
||||
<div class="dropbox"><slot/></div>
|
||||
<div class="dropbox"><slot /></div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@ -81,19 +98,21 @@
|
||||
align-items: center;
|
||||
margin: 0 1rem;
|
||||
height: 2.25rem;
|
||||
border-radius: .5rem;
|
||||
border-radius: 0.5rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
min-width: 1rem;
|
||||
color: var(--theme-content-trans-color);
|
||||
margin: 0 1.125rem 0 .625rem;
|
||||
&.sub { margin: 0 .5rem 0 2.75rem }
|
||||
margin: 0 1.125rem 0 0.625rem;
|
||||
&.sub {
|
||||
margin: 0 0.5rem 0 2.75rem;
|
||||
}
|
||||
}
|
||||
.label {
|
||||
flex-grow: 1;
|
||||
margin-right: .75rem;
|
||||
margin-right: 0.75rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@ -106,31 +125,34 @@
|
||||
}
|
||||
}
|
||||
.tool {
|
||||
margin-right: .75rem;
|
||||
margin-right: 0.75rem;
|
||||
visibility: hidden;
|
||||
}
|
||||
.counter {
|
||||
margin-right: .75rem;
|
||||
margin-right: 0.75rem;
|
||||
font-weight: 600;
|
||||
font-size: .75rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
|
||||
&:hover, &.hovered {
|
||||
&:hover,
|
||||
&.hovered {
|
||||
background-color: var(--theme-button-bg-enabled);
|
||||
.tool {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.selected {
|
||||
background-color: var(--theme-menu-selection);
|
||||
&:hover { background-color: var(--theme-button-bg-enabled); }
|
||||
&:hover {
|
||||
background-color: var(--theme-button-bg-enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropbox {
|
||||
height: auto;
|
||||
margin-bottom: .5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -24,11 +24,11 @@
|
||||
export let icon: Asset
|
||||
export let title: string
|
||||
export let notifications = 0
|
||||
export let actions: Action[] = []
|
||||
export let actions: () => Promise<Action[]> = async () => []
|
||||
export let selected: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
</script>
|
||||
|
||||
<TreeElement {_id} {icon} {title} {notifications} {selected} {actions} collapsed on:click={() => {dispatch('click')}}/>
|
||||
<TreeElement {_id} {icon} {title} {notifications} {selected} {actions} collapsed on:click={() => { dispatch('click') }}/>
|
||||
|
@ -19,7 +19,7 @@
|
||||
import TreeElement from './TreeElement.svelte'
|
||||
|
||||
export let label: IntlString
|
||||
export let actions: Action[] = []
|
||||
export let actions: () => Promise<Action[]> = async () => []
|
||||
export let notifications = 0
|
||||
export let collapsed = false
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user