UBERF-4248: Task type (#4042)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2023-12-14 23:26:02 +07:00
committed by GitHub
parent 58b841518a
commit 19d6764250
196 changed files with 4690 additions and 2524 deletions
@@ -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>