mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-21 17:17:54 +02:00
UBERF-4248: Task type (#4042)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -28,34 +28,29 @@
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="antiNav-element"
|
||||
class="antiNav-element flex-row-center flex-between"
|
||||
class:selected
|
||||
class:expandable
|
||||
on:click|stopPropagation={() => {
|
||||
dispatch('click')
|
||||
}}
|
||||
>
|
||||
<div class="an-element__icon">
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} />
|
||||
{/if}
|
||||
<div class="flex-row-center flex flex-between flex-grow">
|
||||
<div class="flex-row-center">
|
||||
<div class="an-element__icon">
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="an-element__label" class:trans-title={expandable}>
|
||||
{#if label}<Label {label} />{/if}
|
||||
</span>
|
||||
</div>
|
||||
<slot name="tools" />
|
||||
</div>
|
||||
<span class="an-element__label">
|
||||
{#if label}<Label {label} />{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.expandable {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '▶';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.5rem;
|
||||
font-size: 0.375rem;
|
||||
color: var(--dark-color);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -26,33 +26,34 @@
|
||||
RefTo,
|
||||
Type
|
||||
} from '@hcengineering/core'
|
||||
import { getResource, IntlString } from '@hcengineering/platform'
|
||||
import presentation, { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import { IntlString, getResource } from '@hcengineering/platform'
|
||||
import presentation, { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
Action,
|
||||
ActionIcon,
|
||||
AnySvelteComponent,
|
||||
CircleButton,
|
||||
Component,
|
||||
getEventPositionElement,
|
||||
Icon,
|
||||
IconAdd,
|
||||
IconDelete,
|
||||
IconEdit,
|
||||
IconMoreV,
|
||||
IconMoreV2,
|
||||
Label,
|
||||
Menu,
|
||||
getEventPositionElement,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { getContextActions } from '@hcengineering/view-resources/src/actions'
|
||||
import { getContextActions } from '@hcengineering/view-resources'
|
||||
import settings from '../plugin'
|
||||
import CreateAttribute from './CreateAttribute.svelte'
|
||||
import EditAttribute from './EditAttribute.svelte'
|
||||
import EditClassLabel from './EditClassLabel.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let ofClass: Ref<Class<Doc>> | undefined
|
||||
export let ofClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let useOfClassAttributes = true
|
||||
export let showTitle = true
|
||||
export let showCreate = true
|
||||
|
||||
export let attributeMapper:
|
||||
| {
|
||||
@@ -60,7 +61,7 @@
|
||||
label: IntlString
|
||||
props: Record<string, any>
|
||||
}
|
||||
| undefined
|
||||
| undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@@ -77,9 +78,10 @@
|
||||
function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] {
|
||||
const cl = hierarchy.getClass(_class)
|
||||
const attributes = Array.from(
|
||||
hierarchy.getAllAttributes(_class, _class === ofClass ? core.class.Doc : cl.extends).values()
|
||||
hierarchy
|
||||
.getAllAttributes(_class, _class === ofClass && useOfClassAttributes ? core.class.Doc : cl.extends)
|
||||
.values()
|
||||
)
|
||||
// const filtred = attributes.filter((p) => !p.hidden)
|
||||
return attributes
|
||||
}
|
||||
|
||||
@@ -89,19 +91,19 @@
|
||||
attributes = getCustomAttributes(_class)
|
||||
})
|
||||
|
||||
function update () {
|
||||
function update (): void {
|
||||
attributes = getCustomAttributes(_class)
|
||||
}
|
||||
|
||||
function createAttribute () {
|
||||
export function createAttribute (): void {
|
||||
showPopup(CreateAttribute, { _class }, 'top', update)
|
||||
}
|
||||
|
||||
async function editAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
||||
export async function editAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
||||
showPopup(EditAttribute, { attribute, exist }, 'top', update)
|
||||
}
|
||||
|
||||
async function removeAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
||||
export async function removeAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
@@ -110,7 +112,7 @@
|
||||
},
|
||||
'top',
|
||||
async (result) => {
|
||||
if (result) {
|
||||
if (result != null) {
|
||||
await client.remove(attribute)
|
||||
update()
|
||||
}
|
||||
@@ -118,7 +120,7 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function showMenu (ev: MouseEvent, attribute: AnyAttribute) {
|
||||
async function showMenu (ev: MouseEvent, attribute: AnyAttribute): Promise<void> {
|
||||
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exists: true } })) !== undefined
|
||||
|
||||
const actions: Action[] = [
|
||||
@@ -126,16 +128,16 @@
|
||||
label: presentation.string.Edit,
|
||||
icon: IconEdit,
|
||||
action: async () => {
|
||||
editAttribute(attribute, exist)
|
||||
await editAttribute(attribute, exist)
|
||||
}
|
||||
}
|
||||
]
|
||||
if (attribute.isCustom) {
|
||||
if (attribute.isCustom === true) {
|
||||
actions.push({
|
||||
label: presentation.string.Remove,
|
||||
icon: IconDelete,
|
||||
action: async () => {
|
||||
removeAttribute(attribute, exist)
|
||||
await removeAttribute(attribute, exist)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -146,7 +148,7 @@
|
||||
icon: it.icon,
|
||||
action: async (_: any, evt: Event) => {
|
||||
const r = await getResource(it.action)
|
||||
r(attribute, evt, it.actionProps)
|
||||
await r(attribute, evt, it.actionProps)
|
||||
}
|
||||
}))
|
||||
)
|
||||
@@ -176,106 +178,105 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center fs-title mb-3">
|
||||
{#if clazz?.icon}
|
||||
<div class="mr-2 flex">
|
||||
<Icon icon={clazz.icon} size={'medium'} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<Icon icon={IconAdd} size={'x-small'} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if clazz}
|
||||
<Label label={clazz.label} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<div class="ml-2">
|
||||
<ActionIcon icon={IconEdit} size="small" action={editLabel} />
|
||||
{#if showTitle}
|
||||
<div class="flex-row-center fs-title mb-3">
|
||||
{#if clazz?.icon}
|
||||
<div class="mr-2 flex">
|
||||
<Icon icon={clazz.icon} size={'medium'} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<Icon icon={IconAdd} size={'x-small'} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-between trans-title mb-3">
|
||||
<Label label={settings.string.Attributes} />
|
||||
<CircleButton icon={IconAdd} size="medium" on:click={createAttribute} />
|
||||
</div>
|
||||
<table class="antiTable">
|
||||
<thead class="scroller-thead">
|
||||
<tr class="scroller-thead__tr">
|
||||
<!-- <th>Field name</th> -->
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={settings.string.Attribute} />
|
||||
{#if clazz}
|
||||
<Label label={clazz.label} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<div class="ml-2">
|
||||
<ActionIcon icon={IconEdit} size="small" action={editLabel} />
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={settings.string.Type} />
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={settings.string.Visibility} />
|
||||
</div>
|
||||
</th>
|
||||
<th>
|
||||
<div class="antiTable-cells">
|
||||
<Label label={settings.string.Custom} />
|
||||
</div>
|
||||
</th>
|
||||
{#if attributeMapper}
|
||||
<th>
|
||||
<Label label={attributeMapper.label} />
|
||||
</th>
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each attributes as attr}
|
||||
{@const attrType = getAttrType(attr.type)}
|
||||
<tr
|
||||
class="antiTable-body__row"
|
||||
on:contextmenu={(ev) => {
|
||||
ev.preventDefault()
|
||||
showMenu(ev, attr)
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<div class="antiTable-cells__firstCell whitespace-nowrap">
|
||||
<Label label={attr.label} />
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div id="context-menu" class="antiTable-cells__firstCell-menuRow" on:click={(ev) => showMenu(ev, attr)}>
|
||||
<IconMoreV size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if showCreate}
|
||||
<div class="flex-between trans-title mb-3">
|
||||
<Label label={settings.string.Attributes} />
|
||||
<CircleButton icon={IconAdd} size="medium" on:click={createAttribute} />
|
||||
</div>
|
||||
{/if}
|
||||
{#each attributes as attr, i}
|
||||
{@const attrType = getAttrType(attr.type)}
|
||||
<tr
|
||||
class="antiTable-body__row"
|
||||
on:contextmenu={(ev) => {
|
||||
ev.preventDefault()
|
||||
void showMenu(ev, attr)
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
{#if i === 0 && clazz?.label !== undefined}
|
||||
<div class="trans-title">
|
||||
<Label label={clazz.label} />
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<div class="antiTable-cells__firstCell whitespace-nowrap flex-row-center">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div id="context-menu" on:click={(ev) => showMenu(ev, attr)}>
|
||||
<div class="p-1">
|
||||
<IconMoreV2 size={'medium'} />
|
||||
</div>
|
||||
</div>
|
||||
{#if attr.icon !== undefined}
|
||||
<div class="p-1">
|
||||
<Icon icon={attr.icon} size={'small'} />
|
||||
</div>
|
||||
</td>
|
||||
<td class="select-text whitespace-nowrap">
|
||||
<Label label={attr.type.label} />
|
||||
{#if attrType !== undefined}
|
||||
: <Label label={attrType} />
|
||||
{/if}
|
||||
{#if attr.type._class === core.class.EnumOf}
|
||||
{#await getEnumName(attr.type) then name}
|
||||
{#if name}
|
||||
: {name}
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
{#if attr.hidden}
|
||||
<Label label={settings.string.Hidden} />
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<Component is={view.component.BooleanTruePresenter} props={{ value: attr.isCustom ?? false }} />
|
||||
</td>
|
||||
{#if attributeMapper}
|
||||
<td>
|
||||
<svelte:component this={attributeMapper.component} {...attributeMapper.props} attribute={attr} />
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{#if attr.isCustom}
|
||||
<div class="trans-title p-1">
|
||||
<Label label={settings.string.Custom} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class:accent={!attr.hidden}>
|
||||
<Label label={attr.label} />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="select-text whitespace-nowrap trans-title text-xs text-right" style:padding-right={'1rem !important'}>
|
||||
<Label label={attr.type.label} />
|
||||
{#if attrType !== undefined}
|
||||
: <Label label={attrType} />
|
||||
{/if}
|
||||
{#if attr.type._class === core.class.EnumOf}
|
||||
{#await getEnumName(attr.type) then name}
|
||||
{#if name}
|
||||
: {name}
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
</td>
|
||||
{#if attributeMapper}
|
||||
<td>
|
||||
<svelte:component this={attributeMapper.component} {...attributeMapper.props} attribute={attr} />
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
{#if attributes.length === 0}
|
||||
<tr class="antiTable-body__row">
|
||||
<td>
|
||||
<div class="trans-title">
|
||||
{#if clazz}
|
||||
<Label label={clazz.label} />
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td class="select-text whitespace-nowrap"> </td>
|
||||
<td> </td>
|
||||
{#if attributeMapper}
|
||||
<td> </td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/if}
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
<script lang="ts">
|
||||
import { Class, ClassifierKind, Doc, Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getEventPositionElement, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||
import { Icon, IconAdd, getEventPositionElement, showPopup } from '@hcengineering/ui'
|
||||
import { ContextMenu } from '@hcengineering/view-resources'
|
||||
import ObjectPresenter from '@hcengineering/view-resources/src/components/ObjectPresenter.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import settings from '../plugin'
|
||||
|
||||
@@ -25,10 +26,10 @@
|
||||
export let ofClass: Ref<Class<Doc>> | undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function getDescendants (_class: Ref<Class<Doc>>): Ref<Class<Doc>>[] {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const result: Ref<Class<Doc>>[] = []
|
||||
const desc = hierarchy.getDescendants(_class)
|
||||
const vars = [ClassifierKind.MIXIN]
|
||||
@@ -55,7 +56,7 @@
|
||||
</script>
|
||||
|
||||
{#each classes as cl}
|
||||
{@const clazz = hierarchy.getClass(cl)}
|
||||
{@const clazz = client.getHierarchy().getClass(cl)}
|
||||
{@const desc = getDescendants(cl)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
@@ -69,16 +70,18 @@
|
||||
showContextMenu(evt, clazz)
|
||||
}}
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-row-center">
|
||||
{#if clazz.icon}
|
||||
<div class="mr-2 flex">
|
||||
<div class="mr-1 flex">
|
||||
<Icon icon={clazz.icon} size={'medium'} />
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
{#if clazz.kind === ClassifierKind.MIXIN && client.getHierarchy().hasMixin(clazz, settings.mixin.UserMixin)}
|
||||
<Icon icon={IconAdd} size={'x-small'} fill={'var(--theme-dark-color)'} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<span class="overflow-label caption-color"><Label label={clazz.label} /></span>
|
||||
<span class="overflow-label caption-color">
|
||||
<ObjectPresenter _class={clazz._class} objectId={clazz._id} value={clazz} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{#if desc.length}
|
||||
|
||||
@@ -16,21 +16,22 @@
|
||||
import core, { Class, Doc, Obj, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { AnySvelteComponent, getLocation, Icon, Label, navigate } from '@hcengineering/ui'
|
||||
import { AnySvelteComponent, Icon, Label, getLocation, navigate } from '@hcengineering/ui'
|
||||
import setting from '../plugin'
|
||||
import { filterDescendants } from '../utils'
|
||||
import ClassAttributes from './ClassAttributes.svelte'
|
||||
import ClassHierarchy from './ClassHierarchy.svelte'
|
||||
|
||||
export let ofClass: Ref<Class<Obj>> | undefined
|
||||
export let ofClass: Ref<Class<Obj>> | undefined = undefined
|
||||
export let attributeMapper:
|
||||
| {
|
||||
component: AnySvelteComponent
|
||||
label: IntlString
|
||||
props: Record<string, any>
|
||||
}
|
||||
| undefined
|
||||
| undefined = undefined
|
||||
export let withoutHeader = false
|
||||
export let useOfClassAttributes = true
|
||||
|
||||
const loc = getLocation()
|
||||
const client = getClient()
|
||||
@@ -47,26 +48,38 @@
|
||||
const clQuery = createQuery()
|
||||
|
||||
let classes: Ref<Class<Doc>>[] = []
|
||||
clQuery.query(core.class.Class, {}, (res) => {
|
||||
classes = filterDescendants(hierarchy, ofClass, res)
|
||||
let rawClasses: Class<Doc>[] = []
|
||||
|
||||
if (ofClass !== undefined) {
|
||||
// We need to include all possible mixins as well
|
||||
for (const ancestor of hierarchy.getAncestors(ofClass)) {
|
||||
if (ancestor === ofClass) {
|
||||
continue
|
||||
}
|
||||
const mixins = hierarchy.getDescendants(ancestor).filter((it) => hierarchy.isMixin(it))
|
||||
for (const m of mixins) {
|
||||
const mm = hierarchy.getClass(m)
|
||||
if (!classes.includes(m) && mm.extends === ancestor && mm.label !== undefined) {
|
||||
// Check if parent of
|
||||
classes.push(m)
|
||||
}
|
||||
clQuery.query(core.class.Class, {}, (res) => {
|
||||
rawClasses = res
|
||||
})
|
||||
|
||||
$: classes = filterDescendants(hierarchy, ofClass, rawClasses)
|
||||
$: if (ofClass !== undefined) {
|
||||
// We need to include all possible mixins as well
|
||||
for (const ancestor of hierarchy.getAncestors(ofClass)) {
|
||||
if (ancestor === ofClass) {
|
||||
continue
|
||||
}
|
||||
const mixins = hierarchy.getDescendants(ancestor).filter((it) => hierarchy.isMixin(it))
|
||||
for (const m of mixins) {
|
||||
const mm = hierarchy.getClass(m)
|
||||
if (
|
||||
!classes.includes(m) &&
|
||||
mm.extends === ancestor &&
|
||||
mm.label !== undefined &&
|
||||
client.getHierarchy().hasMixin(mm, setting.mixin.Editable)
|
||||
) {
|
||||
// Check if parent of
|
||||
classes.push(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$: if (ofClass !== undefined && _class !== undefined && !client.getHierarchy().isDerived(_class, ofClass)) {
|
||||
_class = ofClass
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiComponent">
|
||||
@@ -91,7 +104,11 @@
|
||||
</div>
|
||||
<div class="ac-column max">
|
||||
{#if _class !== undefined}
|
||||
<ClassAttributes {_class} {ofClass} {attributeMapper} />
|
||||
<table class="antiTable">
|
||||
<tbody>
|
||||
<ClassAttributes {_class} {ofClass} {attributeMapper} {useOfClassAttributes} />
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -95,6 +95,8 @@
|
||||
index = e.detail?.index
|
||||
defaultValue = e.detail?.defaultValue
|
||||
}
|
||||
|
||||
$: clazz = client.getHierarchy().getClass(_class)
|
||||
</script>
|
||||
|
||||
<Card
|
||||
@@ -106,6 +108,13 @@
|
||||
}}
|
||||
on:changeContent
|
||||
>
|
||||
<svelte:fragment slot="title">
|
||||
<div class="flex-row-center">
|
||||
<Label label={setting.string.CreatingAttribute} />
|
||||
<div class="p-1">></div>
|
||||
<Label label={clazz.label} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="mb-2"><EditBox bind:value={name} placeholder={core.string.Name} /></div>
|
||||
<div class="flex-col mb-2">
|
||||
<div class="flex-row-center flex-grow">
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
})
|
||||
|
||||
async function setInviteSettings () {
|
||||
async function setInviteSettings (): Promise<void> {
|
||||
const newSettings = {
|
||||
expirationTime: expTime,
|
||||
emailMask: mask,
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
{items}
|
||||
selected={account.role?.toString()}
|
||||
on:selected={(e) => {
|
||||
change(account, Number(e.detail))
|
||||
void change(account, Number(e.detail))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
{disabled}
|
||||
kind={'primary'}
|
||||
on:click={() => {
|
||||
save()
|
||||
void save()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import { Button, createFocusManager, EditBox, FocusHandler, Icon, Label, showPopup } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import setting from '../plugin'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let avatarEditor: EditableAvatar
|
||||
@@ -34,17 +35,17 @@
|
||||
onDestroy(
|
||||
employeeByIdStore.subscribe((p) => {
|
||||
const emp = p.get(account.person as Ref<Employee>)
|
||||
if (emp) {
|
||||
if (emp !== undefined) {
|
||||
firstName = getFirstName(emp.name)
|
||||
lastName = getLastName(emp.name)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
async function onAvatarDone (e: any) {
|
||||
async function onAvatarDone (e: any): Promise<void> {
|
||||
if (employee === undefined) return
|
||||
|
||||
if (employee.avatar) {
|
||||
if (employee.avatar != null) {
|
||||
await avatarEditor.removeAvatar(employee.avatar)
|
||||
}
|
||||
const avatar = await avatarEditor.createAvatar()
|
||||
@@ -72,9 +73,9 @@
|
||||
)
|
||||
}
|
||||
|
||||
async function nameChange () {
|
||||
if (employee) {
|
||||
await client.update(employee, {
|
||||
async function nameChange (): Promise<void> {
|
||||
if (employee !== undefined) {
|
||||
await client.diffUpdate(employee, {
|
||||
name: combineName(firstName, lastName)
|
||||
})
|
||||
}
|
||||
@@ -142,7 +143,7 @@
|
||||
icon={setting.icon.Signout}
|
||||
label={setting.string.Leave}
|
||||
on:click={() => {
|
||||
leave()
|
||||
void leave()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -71,8 +71,12 @@
|
||||
}
|
||||
function selectCategory (id: string): void {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path[3] = id
|
||||
loc.path.length = 4
|
||||
if (loc.path[3] === id) {
|
||||
loc.path.length = 3
|
||||
} else {
|
||||
loc.path[3] = id
|
||||
loc.path.length = 4
|
||||
}
|
||||
navigate(loc)
|
||||
}
|
||||
function signOut (): void {
|
||||
@@ -86,7 +90,7 @@
|
||||
setMetadata(presentation.metadata.Token, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
|
||||
setMetadataLocalStorage(login.metadata.LoginEmail, null)
|
||||
closeClient()
|
||||
void closeClient()
|
||||
navigate({ path: [loginId] })
|
||||
}
|
||||
function selectWorkspace (): void {
|
||||
@@ -105,20 +109,43 @@
|
||||
<div class="antiPanel-wrap__content">
|
||||
<NavHeader label={setting.string.Settings} />
|
||||
|
||||
<Scroller shrink>
|
||||
{#each categories as category, i}
|
||||
{#if i > 0 && categories[i - 1].group !== category.group}
|
||||
<Scroller>
|
||||
{#each categories as _category, i}
|
||||
{#if i > 0 && categories[i - 1].group !== _category.group}
|
||||
<div class="antiNav-divider short line" />
|
||||
{/if}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
expandable={category._id === setting.ids.Setting}
|
||||
icon={_category.icon}
|
||||
label={_category.label}
|
||||
selected={_category.name === categoryId}
|
||||
expandable={_category.expandable ?? _category._id === setting.ids.Setting}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
selectCategory(_category.name)
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<svelte:fragment slot="tools">
|
||||
{#if _category.extraComponents?.tools}
|
||||
<Component
|
||||
is={_category.extraComponents?.tools}
|
||||
props={{
|
||||
visibleNav,
|
||||
kind: 'tools',
|
||||
categoryName: _category.name
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</CategoryElement>
|
||||
{#if _category.extraComponents?.navigation}
|
||||
<Component
|
||||
is={_category.extraComponents?.navigation}
|
||||
props={{
|
||||
visibleNav,
|
||||
kind: 'navigation',
|
||||
categoryName: _category.name
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="antiNav-space" />
|
||||
</Scroller>
|
||||
|
||||
@@ -17,21 +17,12 @@
|
||||
import { AccountRole, getCurrentAccount } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import setting, { SettingsCategory } from '@hcengineering/setting'
|
||||
import {
|
||||
Component,
|
||||
Label,
|
||||
Location,
|
||||
Scroller,
|
||||
Separator,
|
||||
defineSeparators,
|
||||
getCurrentResolvedLocation,
|
||||
navigate,
|
||||
resolvedLocationStore,
|
||||
settingsSeparators
|
||||
} from '@hcengineering/ui'
|
||||
import { Component, Location, getCurrentResolvedLocation, navigate, resolvedLocationStore } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import CategoryElement from './CategoryElement.svelte'
|
||||
|
||||
export let kind: 'navigation' | undefined
|
||||
export let categoryName: string
|
||||
let category: SettingsCategory | undefined
|
||||
let categoryId: string = ''
|
||||
|
||||
@@ -66,42 +57,38 @@
|
||||
|
||||
function selectCategory (id: string): void {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path[4] = id
|
||||
loc.path.length = 5
|
||||
loc.path[3] = categoryName
|
||||
if (loc.path[4] === id) {
|
||||
loc.path.length = 4
|
||||
} else {
|
||||
loc.path[4] = id
|
||||
loc.path.length = 5
|
||||
}
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
defineSeparators('settingWorkspace', settingsSeparators)
|
||||
</script>
|
||||
|
||||
<div class="flex h-full clear-mins">
|
||||
{#if visibleNav}
|
||||
<div class="antiPanel-navigator filledNav indent">
|
||||
<div class="antiPanel-wrap__content">
|
||||
<div class="antiNav-header overflow-label">
|
||||
<Label label={setting.string.WorkspaceSetting} />
|
||||
</div>
|
||||
<Scroller shrink>
|
||||
{#each categories as category}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<div class="antiNav-space" />
|
||||
</Scroller>
|
||||
</div>
|
||||
{#if kind === 'navigation'}
|
||||
<div class="ml-4 mt-2">
|
||||
<div class="antiPanel-element">
|
||||
{#each categories as category}
|
||||
<CategoryElement
|
||||
icon={category.icon}
|
||||
label={category.label}
|
||||
selected={category.name === categoryId}
|
||||
on:click={() => {
|
||||
selectCategory(category.name)
|
||||
}}
|
||||
/>
|
||||
{#if category.name === categoryId && category.extraComponents?.navigation}
|
||||
<Component
|
||||
is={category.extraComponents?.navigation}
|
||||
props={{ kind: 'navigation', categoryName: categoryId }}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<Separator name={'settingWorkspace'} index={0} color={'var(--theme-navpanel-border)'} />
|
||||
{/if}
|
||||
|
||||
<div class="antiPanel-component filled">
|
||||
{#if category}
|
||||
<Component is={category.component} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else if category}
|
||||
<Component is={category.component} props={{ kind: 'content' }} />
|
||||
{/if}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
<!--
|
||||
// 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 { createQuery, hasResource } from '@hcengineering/presentation'
|
||||
import { Label, Component } from '@hcengineering/ui'
|
||||
import task, { ProjectTypeCategory } from '@hcengineering/task'
|
||||
import setting from '@hcengineering/setting'
|
||||
import IconArrowRight from '../icons/ArrowRight.svelte'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
|
||||
export let category: ProjectTypeCategory | undefined
|
||||
export let categoryId: Ref<ProjectTypeCategory> | undefined
|
||||
let categories: ProjectTypeCategory[] = []
|
||||
const query = createQuery()
|
||||
$: query.query(task.class.ProjectTypeCategory, {}, (result) => {
|
||||
categories = result
|
||||
if (categoryId !== undefined) {
|
||||
category = categories.find((p) => p._id === categoryId)
|
||||
}
|
||||
})
|
||||
|
||||
$: if (category === undefined && categories.length > 0) {
|
||||
category = categories.filter((f) => hasResource(f.icon))[0]
|
||||
}
|
||||
|
||||
function select (item: ProjectTypeCategory) {
|
||||
category = item
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-between trans-title header mb-3">
|
||||
<Label label={setting.string.Categories} />
|
||||
</div>
|
||||
<div class="flex-col overflow-y-auto">
|
||||
{#each categories as f (f._id)}
|
||||
{#if hasResource(f.icon)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="flex-between item"
|
||||
class:selected={f._id === category?._id}
|
||||
on:click={() => {
|
||||
select(f)
|
||||
}}
|
||||
>
|
||||
<div class="icon flex-no-shrink mr-4">
|
||||
<Component is={f.icon} />
|
||||
</div>
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="fs-title overflow-label">
|
||||
<Label label={f.name} />
|
||||
</div>
|
||||
<div class="text-sm content-dark-color overflow-label">
|
||||
<Label label={f.description} />
|
||||
</div>
|
||||
</div>
|
||||
{#if f._id === category?._id}
|
||||
<div class="caption-color ml-4"><IconArrowRight size={'small'} /></div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.header {
|
||||
min-height: 1.75rem;
|
||||
}
|
||||
.item {
|
||||
padding: 1.25rem 1rem 1.25rem 1.25rem;
|
||||
background-color: var(--theme-button-default);
|
||||
border: 1px solid var(--theme-button-border);
|
||||
border-radius: 0.75rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--theme-button-hovered);
|
||||
}
|
||||
&.selected {
|
||||
background-color: var(--theme-button-pressed);
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
.item + .item {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,63 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022, 2023 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 task, { ProjectType, ProjectTypeCategory } from '@hcengineering/task'
|
||||
import { Component, Icon, Label, navigate, resolvedLocationStore } from '@hcengineering/ui'
|
||||
import setting from '../../plugin'
|
||||
|
||||
import Categories from './Categories.svelte'
|
||||
import Types from './Types.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
|
||||
let category: ProjectTypeCategory | undefined
|
||||
let type: ProjectType | undefined
|
||||
let categoryId: Ref<ProjectTypeCategory> | undefined
|
||||
let typeId: Ref<ProjectType> | undefined
|
||||
|
||||
onDestroy(
|
||||
resolvedLocationStore.subscribe((p) => {
|
||||
if (p.query != null) {
|
||||
categoryId = (p.query?.categoryId as Ref<ProjectTypeCategory>) ?? undefined
|
||||
typeId = (p.query?.typeId as Ref<ProjectType>) ?? undefined
|
||||
delete p.query
|
||||
navigate(p, false)
|
||||
}
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="antiComponent">
|
||||
<div class="ac-header short divide">
|
||||
<div class="ac-header__icon"><Icon icon={task.icon.ManageTemplates} size={'medium'} /></div>
|
||||
<div class="ac-header__title"><Label label={setting.string.ManageProjects} /></div>
|
||||
</div>
|
||||
<div class="ac-body columns hScroll">
|
||||
<div class="ac-column">
|
||||
<Categories bind:category {categoryId} />
|
||||
</div>
|
||||
<div class="ac-column">
|
||||
{#if category !== undefined}
|
||||
<Types {category} bind:type {typeId} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ac-column max">
|
||||
{#if type !== undefined}
|
||||
<Component is={task.component.ProjectEditor} props={{ type, category }} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,110 +0,0 @@
|
||||
<!--
|
||||
// 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 core, { IdMap, Ref, toIdMap } from '@hcengineering/core'
|
||||
import { AttributeEditor, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import task, { ProjectStatus, ProjectType, ProjectTypeCategory, createState } from '@hcengineering/task'
|
||||
import { CircleButton, IconAdd, IconMoreH, Label, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import { ContextMenu } from '@hcengineering/view-resources'
|
||||
|
||||
export let category: ProjectTypeCategory
|
||||
export let type: ProjectType | undefined
|
||||
export let typeId: Ref<ProjectType> | undefined
|
||||
|
||||
let types: ProjectType[] = []
|
||||
let typeMap: IdMap<ProjectType> = new Map()
|
||||
const query = createQuery()
|
||||
$: if (category !== undefined) {
|
||||
query.query(task.class.ProjectType, { category: category._id, archived: false }, (result) => {
|
||||
types = result
|
||||
})
|
||||
} else {
|
||||
query.unsubscribe()
|
||||
}
|
||||
|
||||
$: if (typeId === undefined && types.length > 0) {
|
||||
typeId = types[0]._id
|
||||
}
|
||||
|
||||
$: typeMap = toIdMap(types)
|
||||
$: type = typeId !== undefined ? typeMap.get(typeId) : undefined
|
||||
|
||||
const client = getClient()
|
||||
async function createProjectType () {
|
||||
if (category === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const statuses: ProjectStatus[] = []
|
||||
for (const cat of category.statusCategories) {
|
||||
const statusCategory = await client.findOne(core.class.StatusCategory, {
|
||||
_id: cat
|
||||
})
|
||||
if (statusCategory !== undefined) {
|
||||
const id = await createState(client, category.statusClass, {
|
||||
name: statusCategory.defaultStatusName,
|
||||
ofAttribute: statusCategory.ofAttribute,
|
||||
category: statusCategory._id
|
||||
})
|
||||
statuses.push({ _id: id })
|
||||
}
|
||||
}
|
||||
|
||||
await client.createDoc(task.class.ProjectType, core.space.Space, {
|
||||
category: category._id,
|
||||
name: 'New project type',
|
||||
description: '',
|
||||
private: false,
|
||||
members: [],
|
||||
archived: false,
|
||||
statuses
|
||||
})
|
||||
}
|
||||
|
||||
function select (item: ProjectType) {
|
||||
typeId = item._id
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="create-template" class="flex-between trans-title mb-3">
|
||||
<Label label={task.string.ProjectTypes} />
|
||||
<CircleButton icon={IconAdd} size="medium" on:click={createProjectType} />
|
||||
</div>
|
||||
<div id="templates" class="flex-col overflow-y-auto">
|
||||
{#each types as t (t._id)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="ac-column__list-item"
|
||||
class:selected={t._id === type?._id}
|
||||
on:click={() => {
|
||||
select(t)
|
||||
}}
|
||||
>
|
||||
<AttributeEditor maxWidth={'15rem'} _class={task.class.ProjectType} object={t} key="name" />
|
||||
{#if types.length > 1}
|
||||
<div
|
||||
class="hover-trans"
|
||||
on:click|stopPropagation={(ev) => {
|
||||
showPopup(ContextMenu, { object: t }, eventToHTMLElement(ev), () => {})
|
||||
}}
|
||||
>
|
||||
<IconMoreH size={'medium'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
Reference in New Issue
Block a user