mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-28 04:25:03 +02:00
UBERF-6001: Roles management (#4994)
Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
@@ -14,72 +14,45 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref, generateId } from '@hcengineering/core'
|
||||
import { Card, getClient, hasResource } from '@hcengineering/presentation'
|
||||
import { SpaceTypeDescriptor, generateId } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { ProjectTypeDescriptor, createProjectType } from '@hcengineering/task'
|
||||
import { DropdownLabelsIntl, EditBox, ToggleWithLabel } from '@hcengineering/ui'
|
||||
import { ToggleWithLabel } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import task from '../../plugin'
|
||||
import { Resource } from '@hcengineering/platform'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let name: string = ''
|
||||
let classic: boolean = true
|
||||
export let descriptor: SpaceTypeDescriptor
|
||||
export let name: string = ''
|
||||
export const handleTypeCreated: () => Promise<void> = createType
|
||||
|
||||
let descriptor: ProjectTypeDescriptor | undefined = undefined
|
||||
let classic: boolean = true
|
||||
$: projDescriptor = descriptor as ProjectTypeDescriptor
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function createType (): Promise<void> {
|
||||
if (descriptor === undefined) {
|
||||
if (projDescriptor === undefined) {
|
||||
return
|
||||
}
|
||||
await createProjectType(
|
||||
client,
|
||||
{
|
||||
name,
|
||||
descriptor: descriptor._id,
|
||||
descriptor: projDescriptor._id,
|
||||
description: '',
|
||||
tasks: [],
|
||||
classic: descriptor.allowedClassic === true ? classic : false
|
||||
roles: 0,
|
||||
classic: projDescriptor.allowedClassic === true ? classic : false
|
||||
},
|
||||
[],
|
||||
generateId()
|
||||
)
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
const descriptors = client
|
||||
.getModel()
|
||||
.findAllSync(task.class.ProjectTypeDescriptor, {})
|
||||
.filter((p) => hasResource(p._id as any as Resource<any>))
|
||||
const items = descriptors.map((it) => ({
|
||||
label: it.name,
|
||||
id: it._id
|
||||
}))
|
||||
|
||||
function selectType (evt: CustomEvent<Ref<ProjectTypeDescriptor>>): void {
|
||||
descriptor = descriptors.find((it) => it._id === evt.detail)
|
||||
}
|
||||
|
||||
descriptor = descriptors[0]
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={task.string.CreateProjectType}
|
||||
canSave={name.trim().length > 0 && descriptor !== undefined}
|
||||
okAction={createType}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
on:changeContent
|
||||
>
|
||||
<div class="flex-col flex-gap-2">
|
||||
<EditBox bind:value={name} placeholder={task.string.ProjectType} />
|
||||
<DropdownLabelsIntl {items} on:selected={selectType} />
|
||||
{#if descriptor?.allowedClassic === true}
|
||||
<ToggleWithLabel label={task.string.ClassicProject} bind:on={classic} />
|
||||
{/if}
|
||||
</div>
|
||||
</Card>
|
||||
{#if projDescriptor?.allowedClassic === true}
|
||||
<ToggleWithLabel label={task.string.ClassicProject} bind:on={classic} />
|
||||
{/if}
|
||||
|
||||
@@ -1,76 +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 { IdMap, Ref, WithLookup, toIdMap } from '@hcengineering/core'
|
||||
import task, { ProjectType } from '@hcengineering/task'
|
||||
import { Location, getCurrentResolvedLocation, navigate, resolvedLocationStore } from '@hcengineering/ui'
|
||||
|
||||
import { createQuery, hasResource } from '@hcengineering/presentation'
|
||||
import { onDestroy } from 'svelte'
|
||||
import Types from './Types.svelte'
|
||||
import { Resource } from '@hcengineering/platform'
|
||||
import { clearSettingsStore } from '@hcengineering/setting-resources'
|
||||
|
||||
export let kind: 'navigation' | 'tools' | undefined
|
||||
export let categoryName: string
|
||||
let type: WithLookup<ProjectType> | undefined
|
||||
let typeId: Ref<ProjectType> | undefined
|
||||
|
||||
onDestroy(
|
||||
resolvedLocationStore.subscribe((loc) => {
|
||||
void (async (loc: Location): Promise<void> => {
|
||||
typeId = loc.path[4] as Ref<ProjectType>
|
||||
})(loc)
|
||||
})
|
||||
)
|
||||
|
||||
function selectProjectType (id: string): void {
|
||||
clearSettingsStore()
|
||||
const loc = getCurrentResolvedLocation()
|
||||
loc.path[3] = categoryName
|
||||
loc.path[4] = id
|
||||
loc.path.length = 5
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
let types: WithLookup<ProjectType>[] = []
|
||||
let typeMap: IdMap<WithLookup<ProjectType>> = new Map()
|
||||
const query = createQuery()
|
||||
$: query.query(
|
||||
task.class.ProjectType,
|
||||
{ archived: false },
|
||||
(result) => {
|
||||
types = result.filter((p) => hasResource(p.descriptor as any as Resource<any>))
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
descriptor: task.class.ProjectTypeDescriptor
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
$: typeMap = toIdMap(types)
|
||||
$: type = typeId !== undefined ? typeMap.get(typeId) : undefined
|
||||
</script>
|
||||
|
||||
<Types
|
||||
{type}
|
||||
{typeId}
|
||||
{types}
|
||||
on:change={(evt) => {
|
||||
selectProjectType(evt.detail)
|
||||
}}
|
||||
/>
|
||||
@@ -1,51 +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 { Ref, WithLookup } from '@hcengineering/core'
|
||||
import { ProjectType } from '@hcengineering/task'
|
||||
import { Location, resolvedLocationStore, resizeObserver } from '@hcengineering/ui'
|
||||
|
||||
import { onDestroy } from 'svelte'
|
||||
import { typeStore } from '../../'
|
||||
import ProjectEditor from './ProjectEditor.svelte'
|
||||
|
||||
export let visibleNav: boolean = true
|
||||
|
||||
let visibleSecondNav: boolean = true
|
||||
let type: WithLookup<ProjectType> | undefined
|
||||
let typeId: Ref<ProjectType> | undefined
|
||||
|
||||
onDestroy(
|
||||
resolvedLocationStore.subscribe((loc) => {
|
||||
void (async (loc: Location): Promise<void> => {
|
||||
typeId = loc.path[4] as Ref<ProjectType>
|
||||
})(loc)
|
||||
})
|
||||
)
|
||||
|
||||
$: type = typeId !== undefined ? $typeStore.get(typeId) : undefined
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="hulyComponent"
|
||||
use:resizeObserver={(element) => {
|
||||
visibleSecondNav = element.clientWidth > 720
|
||||
}}
|
||||
>
|
||||
{#if type !== undefined}
|
||||
<ProjectEditor {type} descriptor={type.$lookup?.descriptor} {visibleNav} {visibleSecondNav} on:change />
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,353 +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 { ComponentExtensions, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import task, { Project, ProjectType, ProjectTypeDescriptor, Task, TaskType } from '@hcengineering/task'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
|
||||
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import setting from '@hcengineering/setting'
|
||||
import { ClassAttributes, clearSettingsStore, settingsStore } from '@hcengineering/setting-resources'
|
||||
import {
|
||||
Breadcrumbs,
|
||||
ButtonIcon,
|
||||
Component,
|
||||
Header,
|
||||
IconAdd,
|
||||
IconCopy,
|
||||
IconDelete,
|
||||
IconDescription,
|
||||
IconFolder,
|
||||
IconMoreV,
|
||||
IconSquareExpand,
|
||||
Label,
|
||||
Location,
|
||||
ModernButton,
|
||||
ModernEditbox,
|
||||
NavItem,
|
||||
Scroller,
|
||||
Separator,
|
||||
TextArea,
|
||||
defineSeparators,
|
||||
getCurrentResolvedLocation,
|
||||
navigate,
|
||||
resolvedLocationStore,
|
||||
secondNavSeparators
|
||||
} from '@hcengineering/ui'
|
||||
import { showMenu } from '@hcengineering/view-resources'
|
||||
import plugin from '../../plugin'
|
||||
import IconLayers from '../icons/Layers.svelte'
|
||||
import CreateTaskType from '../taskTypes/CreateTaskType.svelte'
|
||||
import TaskTypeEditor from '../taskTypes/TaskTypeEditor.svelte'
|
||||
import TaskTypeIcon from '../taskTypes/TaskTypeIcon.svelte'
|
||||
import TaskTypeKindEditor from '../taskTypes/TaskTypeKindEditor.svelte'
|
||||
|
||||
export let type: ProjectType
|
||||
export let descriptor: ProjectTypeDescriptor | undefined
|
||||
export let visibleNav: boolean = true
|
||||
export let visibleSecondNav: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
|
||||
const projectQuery = createQuery()
|
||||
|
||||
$: if (descriptor === undefined) {
|
||||
void client.findOne(task.class.ProjectTypeDescriptor, { _id: type.descriptor }).then((res) => {
|
||||
descriptor = res
|
||||
})
|
||||
}
|
||||
|
||||
let taskTypes: TaskType[] = []
|
||||
let projects: Project[] = []
|
||||
|
||||
$: query.query(
|
||||
task.class.TaskType,
|
||||
{ _id: { $in: type?.tasks ?? [] } },
|
||||
(res) => {
|
||||
taskTypes = res
|
||||
},
|
||||
{ sort: { _id: SortingOrder.Ascending } }
|
||||
)
|
||||
async function onShortDescriptionChange (value: string): Promise<void> {
|
||||
if (type !== undefined) {
|
||||
await client.diffUpdate(type, { shortDescription: value })
|
||||
}
|
||||
}
|
||||
|
||||
$: projectQuery.query(task.class.Project, { type: type._id }, (res) => {
|
||||
projects = res
|
||||
})
|
||||
|
||||
const statsQuery = createQuery()
|
||||
|
||||
let tasks: Task[] = []
|
||||
|
||||
$: statsQuery.query(
|
||||
task.class.Task,
|
||||
{ kind: { $in: taskTypes.map((it) => it._id) } },
|
||||
(res) => {
|
||||
tasks = res
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
_id: 1,
|
||||
_class: 1,
|
||||
space: 1,
|
||||
status: 1,
|
||||
kind: 1
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// $: spaceCounter = tasks.reduce(
|
||||
// (map, task) => map.set(task.space, (map.get(task.space) ?? 0) + 1),
|
||||
// new Map<Ref<Space>, number>()
|
||||
// )
|
||||
|
||||
$: taskTypeCounter = tasks.reduce(
|
||||
(map, task) => map.set(task.kind, (map.get(task.kind) ?? 0) + 1),
|
||||
new Map<Ref<TaskType>, number>()
|
||||
)
|
||||
|
||||
let selectedTaskTypeId: Ref<TaskType> | undefined
|
||||
|
||||
$: selectedTaskType = taskTypes.find((it) => it._id === selectedTaskTypeId)
|
||||
|
||||
onDestroy(
|
||||
resolvedLocationStore.subscribe((loc) => {
|
||||
void (async (loc: Location): Promise<void> => {
|
||||
selectedTaskTypeId = loc.path[5] as Ref<TaskType>
|
||||
})(loc)
|
||||
})
|
||||
)
|
||||
|
||||
function selectTaskType (id: string | undefined): void {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
if (id !== undefined) {
|
||||
loc.path[5] = id
|
||||
loc.path.length = 6
|
||||
} else {
|
||||
loc.path.length = 5
|
||||
}
|
||||
selectedTaskTypeId = id as Ref<TaskType>
|
||||
clearSettingsStore()
|
||||
navigate(loc)
|
||||
}
|
||||
$: items =
|
||||
selectedTaskType !== undefined
|
||||
? [
|
||||
{ label: plugin.string.ProjectType, icon: descriptor?.icon },
|
||||
{ title: selectedTaskType.name, icon: selectedTaskType.icon }
|
||||
]
|
||||
: [{ label: plugin.string.ProjectType, icon: descriptor?.icon }]
|
||||
|
||||
let scroller: Scroller
|
||||
const navigator: {
|
||||
id: string
|
||||
label: IntlString
|
||||
element?: HTMLElement
|
||||
}[] = [
|
||||
{ id: 'properties', label: setting.string.Properties },
|
||||
{ id: 'tasktypes', label: setting.string.TaskTypes },
|
||||
{ id: 'automations', label: setting.string.Automations },
|
||||
{ id: 'collections', label: setting.string.Collections }
|
||||
]
|
||||
|
||||
defineSeparators('typeSettings', secondNavSeparators)
|
||||
</script>
|
||||
|
||||
{#if type !== undefined && descriptor !== undefined}
|
||||
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
|
||||
<ButtonIcon
|
||||
icon={IconCopy}
|
||||
size={'small'}
|
||||
kind={'secondary'}
|
||||
disabled
|
||||
on:click={(ev) => {
|
||||
// Do copy of type
|
||||
}}
|
||||
/>
|
||||
<ButtonIcon
|
||||
icon={IconDelete}
|
||||
size={'small'}
|
||||
kind={'secondary'}
|
||||
disabled
|
||||
on:click={(ev) => {
|
||||
// Ask for delete
|
||||
}}
|
||||
/>
|
||||
<ButtonIcon
|
||||
icon={IconMoreV}
|
||||
size={'small'}
|
||||
kind={'secondary'}
|
||||
on:click={(ev) => {
|
||||
showMenu(ev, { object: type })
|
||||
}}
|
||||
/>
|
||||
<Breadcrumbs
|
||||
{items}
|
||||
size={'large'}
|
||||
selected={selectedTaskType !== undefined ? 1 : 0}
|
||||
on:select={(event) => {
|
||||
if (event.detail === 0) selectTaskType(undefined)
|
||||
}}
|
||||
>
|
||||
<!-- afterLabel={plugin.string.Published} -->
|
||||
<!-- <span slot="afterLabel">{dateStr}</span> -->
|
||||
</Breadcrumbs>
|
||||
<!-- <svelte:fragment slot="actions">
|
||||
<div class="hulyHeader-buttonsGroup__label font-regular-12">
|
||||
<Label label={plugin.string.LastSave} />
|
||||
<span>{dateStr}</span>
|
||||
</div>
|
||||
<ModernButton kind={'secondary'} label={ui.string.SaveDraft} size={'small'} />
|
||||
<ModernButton kind={'primary'} icon={IconSend} label={ui.string.Publish} size={'small'} />
|
||||
</svelte:fragment> -->
|
||||
</Header>
|
||||
<div class="hulyComponent-content__container columns">
|
||||
{#if selectedTaskTypeId === undefined && visibleSecondNav}
|
||||
<div class="hulyComponent-content__column">
|
||||
<div class="hulyComponent-content__navHeader">
|
||||
<div class="hulyComponent-content__navHeader-menu">
|
||||
<ButtonIcon kind={'tertiary'} icon={IconDescription} size={'small'} inheritColor />
|
||||
</div>
|
||||
</div>
|
||||
{#each navigator as navItem, i (navItem.id)}
|
||||
<NavItem
|
||||
type={'type-anchor-link'}
|
||||
label={navItem.label}
|
||||
on:click={() => {
|
||||
if (i === 0) scroller.scroll(0)
|
||||
else navItem.element?.scrollIntoView()
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
<Separator name={'typeSettings'} index={0} color={'transparent'} />
|
||||
{/if}
|
||||
<div class="hulyComponent-content__column content">
|
||||
<Scroller bind:this={scroller} align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
|
||||
<div class="hulyComponent-content gap">
|
||||
{#if selectedTaskType === undefined}
|
||||
<div class="hulyComponent-content__column-group">
|
||||
<div class="hulyComponent-content__header">
|
||||
<ButtonIcon icon={descriptor.icon} size={'large'} kind={'secondary'} />
|
||||
<ModernButton
|
||||
icon={IconSquareExpand}
|
||||
label={plugin.string.CountProjects}
|
||||
labelParams={{ count: projects.length }}
|
||||
disabled={projects.length === 0}
|
||||
kind={'tertiary'}
|
||||
size={'medium'}
|
||||
hasMenu
|
||||
/>
|
||||
</div>
|
||||
<ModernEditbox
|
||||
kind={'ghost'}
|
||||
size={'large'}
|
||||
label={plugin.string.ProjectTypeTitle}
|
||||
value={type?.name ?? ''}
|
||||
on:blur={(evt) => {
|
||||
if (type !== undefined) {
|
||||
void client.diffUpdate(type, { name: evt.detail })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<TextArea
|
||||
placeholder={plugin.string.Description}
|
||||
width={'100%'}
|
||||
height={'4.5rem'}
|
||||
margin={'var(--spacing-1) var(--spacing-2)'}
|
||||
noFocusBorder
|
||||
bind:value={type.shortDescription}
|
||||
on:change={() => onShortDescriptionChange(type?.shortDescription ?? '')}
|
||||
/>
|
||||
{#if descriptor?.editor}
|
||||
<Component is={descriptor.editor} props={{ type }} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<ClassAttributes ofClass={descriptor.baseClass} _class={type.targetClass} showHierarchy />
|
||||
|
||||
<div bind:this={navigator[1].element} class="hulyTableAttr-container">
|
||||
<div class="hulyTableAttr-header font-medium-12">
|
||||
<IconLayers size={'small'} />
|
||||
<span><Label label={setting.string.TaskTypes} /></span>
|
||||
<ButtonIcon
|
||||
kind={'primary'}
|
||||
icon={IconAdd}
|
||||
size={'small'}
|
||||
on:click={(ev) => {
|
||||
$settingsStore = { id: 'createTaskType', component: CreateTaskType, props: { type, descriptor } }
|
||||
// showPopup(CreateTaskType, { type, descriptor }, 'top')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if taskTypes.length}
|
||||
<div class="hulyTableAttr-content task">
|
||||
{#each taskTypes as taskType}
|
||||
<button
|
||||
class="hulyTableAttr-content__row"
|
||||
on:click|stopPropagation={() => {
|
||||
selectTaskType(taskType._id)
|
||||
}}
|
||||
>
|
||||
<div class="hulyTableAttr-content__row-icon-wrapper">
|
||||
<TaskTypeIcon value={taskType} size={'small'} />
|
||||
</div>
|
||||
{#if taskType.name}
|
||||
<div class="hulyTableAttr-content__row-label font-medium-14">
|
||||
{taskType.name}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="hulyTableAttr-content__row-label grow dark font-regular-14">
|
||||
<TaskTypeKindEditor readonly kind={taskType.kind} />
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div bind:this={navigator[2].element} class="flex-col gapV-8">
|
||||
<ComponentExtensions extension={task.extensions.ProjectEditorExtension} props={{ type }} />
|
||||
</div>
|
||||
|
||||
<div bind:this={navigator[3].element} class="hulyTableAttr-container">
|
||||
<div class="hulyTableAttr-header font-medium-12">
|
||||
<IconFolder size={'small'} />
|
||||
<span><Label label={setting.string.Collections} /></span>
|
||||
<ButtonIcon kind={'primary'} icon={IconAdd} size={'small'} on:click={() => {}} />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<TaskTypeEditor taskType={selectedTaskType} projectType={type} {taskTypes} {taskTypeCounter} />
|
||||
{/if}
|
||||
</div>
|
||||
</Scroller>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.gap {
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
</style>
|
||||
+9
-8
@@ -1,6 +1,5 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022, 2023 Hardcore Engineering Inc.
|
||||
// Copyright © 2024 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
|
||||
@@ -14,12 +13,14 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Button, IconAdd, showPopup } from '@hcengineering/ui'
|
||||
import CreateProjectType from './CreateProjectType.svelte'
|
||||
import { ComponentExtensions } from '@hcengineering/presentation'
|
||||
import { ProjectType, ProjectTypeDescriptor } from '@hcengineering/task'
|
||||
import task from '../../plugin'
|
||||
|
||||
function open () {
|
||||
showPopup(CreateProjectType, {}, 'top')
|
||||
}
|
||||
export let type: ProjectType | undefined
|
||||
export let descriptor: ProjectTypeDescriptor | undefined
|
||||
</script>
|
||||
|
||||
<Button id="new-project-type" icon={IconAdd} kind={'link'} size="small" on:click={open} />
|
||||
{#if descriptor !== undefined && type !== undefined}
|
||||
<ComponentExtensions extension={task.extensions.ProjectEditorExtension} props={{ type }} />
|
||||
{/if}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { ButtonIcon, IconAdd, IconFolder, Label } from '@hcengineering/ui'
|
||||
import { ProjectType, ProjectTypeDescriptor } from '@hcengineering/task'
|
||||
import task from '../../plugin'
|
||||
|
||||
export let type: ProjectType | undefined
|
||||
export let descriptor: ProjectTypeDescriptor | undefined
|
||||
</script>
|
||||
|
||||
{#if descriptor !== undefined}
|
||||
<div class="hulyTableAttr-header font-medium-12">
|
||||
<IconFolder size="small" />
|
||||
<span><Label label={task.string.Collections} /></span>
|
||||
<ButtonIcon kind="primary" icon={IconAdd} size="small" on:click={() => {}} />
|
||||
</div>
|
||||
{/if}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { type ProjectType, type ProjectTypeDescriptor } from '@hcengineering/task'
|
||||
import { SpaceTypeGeneralSectionEditor } from '@hcengineering/setting-resources'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
|
||||
export let type: ProjectType | undefined
|
||||
export let descriptor: ProjectTypeDescriptor | undefined
|
||||
</script>
|
||||
|
||||
<SpaceTypeGeneralSectionEditor {type} {descriptor}>
|
||||
<svelte:fragment slot="extra">
|
||||
{#if descriptor?.editor}
|
||||
<Component is={descriptor.editor} props={{ type }} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</SpaceTypeGeneralSectionEditor>
|
||||
@@ -31,14 +31,9 @@
|
||||
|
||||
let types: ProjectType[] = []
|
||||
const typesQ = createQuery()
|
||||
const query = disabled
|
||||
? {
|
||||
descriptor: { $in: descriptors }
|
||||
}
|
||||
: {
|
||||
descriptor: { $in: descriptors },
|
||||
archived: false
|
||||
}
|
||||
const query = {
|
||||
descriptor: { $in: descriptors }
|
||||
}
|
||||
$: typesQ.query(task.class.ProjectType, query, (result) => {
|
||||
types = result
|
||||
})
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 { SortingOrder } from '@hcengineering/core'
|
||||
import { ButtonIcon, IconAdd, Label, getCurrentResolvedLocation, navigate } from '@hcengineering/ui'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { ProjectType, ProjectTypeDescriptor, TaskType } from '@hcengineering/task'
|
||||
import { clearSettingsStore, settingsStore } from '@hcengineering/setting-resources'
|
||||
|
||||
import IconLayers from '../icons/Layers.svelte'
|
||||
import TaskTypeIcon from '../taskTypes/TaskTypeIcon.svelte'
|
||||
import TaskTypeKindEditor from '../taskTypes/TaskTypeKindEditor.svelte'
|
||||
import CreateTaskType from '../taskTypes/CreateTaskType.svelte'
|
||||
import task from '../../plugin'
|
||||
|
||||
export let type: ProjectType | undefined
|
||||
export let descriptor: ProjectTypeDescriptor | undefined
|
||||
|
||||
let taskTypes: TaskType[] = []
|
||||
const taskTypesQuery = createQuery()
|
||||
$: taskTypesQuery.query(
|
||||
task.class.TaskType,
|
||||
{ _id: { $in: type?.tasks ?? [] } },
|
||||
(res) => {
|
||||
taskTypes = res
|
||||
},
|
||||
{ sort: { _id: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
function handleTaskTypeSelected (id: string | undefined): void {
|
||||
const loc = getCurrentResolvedLocation()
|
||||
if (id !== undefined) {
|
||||
loc.path[5] = 'taskTypes'
|
||||
loc.path[6] = id
|
||||
loc.path.length = 7
|
||||
} else {
|
||||
loc.path.length = 5
|
||||
}
|
||||
|
||||
clearSettingsStore()
|
||||
navigate(loc)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if descriptor !== undefined}
|
||||
<div class="hulyTableAttr-header font-medium-12">
|
||||
<IconLayers size={'small'} />
|
||||
<span><Label label={task.string.TaskTypes} /></span>
|
||||
<ButtonIcon
|
||||
kind="primary"
|
||||
icon={IconAdd}
|
||||
size="small"
|
||||
on:click={(ev) => {
|
||||
$settingsStore = { id: 'createTaskType', component: CreateTaskType, props: { type, descriptor } }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if taskTypes.length}
|
||||
<div class="hulyTableAttr-content task">
|
||||
{#each taskTypes as taskType}
|
||||
<button
|
||||
class="hulyTableAttr-content__row"
|
||||
on:click|stopPropagation={() => {
|
||||
handleTaskTypeSelected(taskType._id)
|
||||
}}
|
||||
>
|
||||
<div class="hulyTableAttr-content__row-icon-wrapper">
|
||||
<TaskTypeIcon value={taskType} size={'small'} />
|
||||
</div>
|
||||
{#if taskType.name}
|
||||
<div class="hulyTableAttr-content__row-label font-medium-14">
|
||||
{taskType.name}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="hulyTableAttr-content__row-label grow dark font-regular-14">
|
||||
<TaskTypeKindEditor readonly kind={taskType.kind} />
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -1,149 +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 { Ref, WithLookup } from '@hcengineering/core'
|
||||
import { ProjectType } from '@hcengineering/task'
|
||||
import { Icon, Label, IconOpenedArrow } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let type: ProjectType | undefined
|
||||
export let typeId: Ref<ProjectType> | undefined
|
||||
|
||||
export let types: WithLookup<ProjectType>[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
function select (item: ProjectType): void {
|
||||
typeId = item._id
|
||||
dispatch('change', typeId)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each types as typeItem}
|
||||
<button
|
||||
class="hulyTaskNavLink-container font-regular-14"
|
||||
class:selected={typeItem._id === typeId}
|
||||
on:click={() => {
|
||||
select(typeItem)
|
||||
}}
|
||||
>
|
||||
<div class="hulyTaskNavLink-avatar">
|
||||
{#if typeItem.$lookup?.descriptor?.icon}
|
||||
<div class="hulyTaskNavLink-icon">
|
||||
<Icon icon={typeItem.$lookup?.descriptor?.icon} size={'small'} fill={'currentColor'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if typeItem.$lookup?.descriptor}
|
||||
<div class="hulyTaskNavLink-content">
|
||||
<span class="hulyTaskNavLink-content__title">{typeItem.name}</span>
|
||||
<span class="hulyTaskNavLink-content__descriptor">
|
||||
<Label label={typeItem.$lookup?.descriptor.name} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if typeItem._id === typeId}
|
||||
<div class="hulyTaskNavLink-icon right">
|
||||
<IconOpenedArrow size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
<style lang="scss">
|
||||
.hulyTaskNavLink-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: 0.75rem;
|
||||
padding: 0 0.75rem 0 0.5rem;
|
||||
height: 3.5rem;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
outline: none;
|
||||
|
||||
&.selected {
|
||||
cursor: auto;
|
||||
}
|
||||
.hulyTaskNavLink-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
min-width: 0;
|
||||
background-color: var(--global-ui-BackgroundColor);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
.hulyTaskNavLink-icon {
|
||||
flex-shrink: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
color: var(--global-secondary-TextColor);
|
||||
|
||||
&.right {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.hulyTaskNavLink-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
&__title,
|
||||
&__descriptor {
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
min-width: 0;
|
||||
}
|
||||
&__title {
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
&__descriptor {
|
||||
color: var(--global-secondary-TextColor);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--global-ui-hover-highlight-BackgroundColor);
|
||||
}
|
||||
&.selected {
|
||||
background-color: var(--global-ui-highlight-BackgroundColor);
|
||||
|
||||
.hulyTaskNavLink-icon {
|
||||
color: var(--global-accent-TextColor);
|
||||
|
||||
&.right {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.hulyTaskNavLink-content .hulyTaskNavLink-content__title {
|
||||
font-weight: 700;
|
||||
color: var(--global-accent-TextColor);
|
||||
}
|
||||
.hulyTaskNavLink-content .hulyTaskNavLink-content__descriptor {
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -143,7 +143,7 @@
|
||||
icon: ofClassClass.icon
|
||||
})
|
||||
|
||||
await client.createDoc(task.class.TaskType, type._id, _taskType, taskTypeId)
|
||||
await client.createDoc(task.class.TaskType, core.space.Model, _taskType, taskTypeId)
|
||||
}
|
||||
|
||||
if (!type.tasks.includes(taskTypeId)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021, 2022, 2023 Hardcore Engineering Inc.
|
||||
// Copyright © 2021, 2022, 2023, 2024 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
|
||||
@@ -14,11 +14,20 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { AttributeEditor, getClient } from '@hcengineering/presentation'
|
||||
import { AttributeEditor, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import task, { ProjectType, TaskType, calculateStatuses, findStatusAttr } from '@hcengineering/task'
|
||||
import { Ref, Status } from '@hcengineering/core'
|
||||
import { Ref, SortingOrder, Status } from '@hcengineering/core'
|
||||
import { Asset, getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Label, showPopup, ButtonIcon, ModernButton, IconSquareExpand, IconAdd, Icon } from '@hcengineering/ui'
|
||||
import {
|
||||
Label,
|
||||
showPopup,
|
||||
ButtonIcon,
|
||||
ModernButton,
|
||||
IconSquareExpand,
|
||||
IconAdd,
|
||||
Icon,
|
||||
Scroller
|
||||
} from '@hcengineering/ui'
|
||||
import { IconPicker, statusStore } from '@hcengineering/view-resources'
|
||||
import { ClassAttributes, settingsStore } from '@hcengineering/setting-resources'
|
||||
import { taskTypeStore } from '../..'
|
||||
@@ -28,31 +37,67 @@
|
||||
import TaskTypeIcon from './TaskTypeIcon.svelte'
|
||||
import plugin from '../../plugin'
|
||||
|
||||
export let projectType: ProjectType
|
||||
export let taskType: TaskType
|
||||
export let taskTypeCounter: Map<Ref<TaskType>, number>
|
||||
export let taskTypes: TaskType[]
|
||||
export let spaceType: ProjectType
|
||||
export let objectId: Ref<TaskType>
|
||||
export let name: string | undefined
|
||||
export let icon: Asset | undefined
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: descriptor = client.getModel().findAllSync(task.class.TaskTypeDescriptor, { _id: taskType?.descriptor })
|
||||
let taskTypes: TaskType[] = []
|
||||
const taskTypesQuery = createQuery()
|
||||
$: taskTypesQuery.query(
|
||||
task.class.TaskType,
|
||||
{ _id: { $in: spaceType?.tasks ?? [] } },
|
||||
(res) => {
|
||||
taskTypes = res
|
||||
},
|
||||
{ sort: { _id: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
$: states = taskType.statuses.map((p) => $statusStore.byId.get(p)).filter((p) => p !== undefined) as Status[]
|
||||
$: taskType = taskTypes.find((tt) => tt._id === objectId)
|
||||
$: name = taskType?.name
|
||||
$: icon = taskType?.icon
|
||||
$: descriptor = client.getModel().findAllSync(task.class.TaskTypeDescriptor, { _id: taskType?.descriptor })
|
||||
$: states = (taskType?.statuses.map((p) => $statusStore.byId.get(p)).filter((p) => p !== undefined) as Status[]) ?? []
|
||||
|
||||
let tasksCounter: number = 0
|
||||
const tasksCounterQuery = createQuery()
|
||||
$: if (taskType !== undefined) {
|
||||
tasksCounterQuery.query(
|
||||
task.class.Task,
|
||||
{ kind: taskType._id },
|
||||
(res) => {
|
||||
tasksCounter = res.length
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
_id: 1
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function selectIcon (el: MouseEvent): void {
|
||||
const icons: Asset[] = [descriptor[0].icon]
|
||||
|
||||
showPopup(
|
||||
IconPicker,
|
||||
{ icon: taskType?.icon, color: taskType?.color, icons, showColor: false },
|
||||
el.target as HTMLElement,
|
||||
async (result) => {
|
||||
if (result !== undefined && result !== null) {
|
||||
if (result !== undefined && result !== null && taskType !== undefined) {
|
||||
await client.update(taskType, { color: result.color, icon: result.icon })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function handleAddStatus (): void {
|
||||
if (taskType === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const icons: Asset[] = []
|
||||
const attr = findStatusAttr(getClient().getHierarchy(), taskType.ofClass)
|
||||
$settingsStore = {
|
||||
@@ -63,7 +108,7 @@
|
||||
taskType,
|
||||
_class: taskType.statusClass,
|
||||
category: task.statusCategory.Active,
|
||||
type: projectType,
|
||||
type: spaceType,
|
||||
ofAttribute: attr._id,
|
||||
icon: undefined,
|
||||
color: 0,
|
||||
@@ -73,93 +118,120 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hulyComponent-content__column-group mt-4">
|
||||
<div class="hulyComponent-content__header mb-6">
|
||||
<div class="flex-row-center gap-1-5">
|
||||
<TaskTypeKindEditor
|
||||
kind={taskType.kind}
|
||||
on:change={(evt) => {
|
||||
void client.diffUpdate(taskType, { kind: evt.detail })
|
||||
}}
|
||||
/>
|
||||
<ButtonIcon
|
||||
icon={TaskTypeIcon}
|
||||
iconProps={{ value: taskType }}
|
||||
size={'large'}
|
||||
kind={'secondary'}
|
||||
on:click={selectIcon}
|
||||
/>
|
||||
{#if taskType !== undefined}
|
||||
<div class="hulyComponent-content__container columns">
|
||||
<div class="hulyComponent-content__column content">
|
||||
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
|
||||
<div class="hulyComponent-content gap">
|
||||
<div class="hulyComponent-content__column-group mt-4">
|
||||
<div class="hulyComponent-content__header mb-6">
|
||||
<div class="flex-row-center gap-1-5">
|
||||
<TaskTypeKindEditor
|
||||
kind={taskType.kind}
|
||||
on:change={(evt) => {
|
||||
if (taskType === undefined) {
|
||||
return
|
||||
}
|
||||
void client.diffUpdate(taskType, { kind: evt.detail })
|
||||
}}
|
||||
/>
|
||||
<ButtonIcon
|
||||
icon={TaskTypeIcon}
|
||||
iconProps={{ value: taskType }}
|
||||
size={'large'}
|
||||
kind={'secondary'}
|
||||
on:click={selectIcon}
|
||||
/>
|
||||
</div>
|
||||
<ModernButton
|
||||
icon={IconSquareExpand}
|
||||
label={plugin.string.CountTasks}
|
||||
labelParams={{ count: tasksCounter }}
|
||||
disabled={tasksCounter === 0}
|
||||
kind={'tertiary'}
|
||||
size={'medium'}
|
||||
hasMenu
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AttributeEditor
|
||||
_class={task.class.TaskType}
|
||||
object={taskType}
|
||||
key="name"
|
||||
editKind={'modern-ghost-large'}
|
||||
/>
|
||||
|
||||
<div class="flex-row-center mt-4 ml-4 mr-4 gap-4">
|
||||
<div class="flex-no-shrink trans-title uppercase">
|
||||
<Label label={getEmbeddedLabel('Parent type restrictions')} />
|
||||
</div>
|
||||
{#if taskType.kind === 'subtask' || taskType.kind === 'both'}
|
||||
<TaskTypeRefEditor
|
||||
label={getEmbeddedLabel('Allowed parents')}
|
||||
value={taskType.allowedAsChildOf}
|
||||
types={taskTypes.filter((it) => it.kind === 'task' || it.kind === 'both')}
|
||||
onChange={(evt) => {
|
||||
if (taskType === undefined) {
|
||||
return
|
||||
}
|
||||
void client.diffUpdate(taskType, { allowedAsChildOf: evt })
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hulyTableAttr-container">
|
||||
<div class="hulyTableAttr-header font-medium-12">
|
||||
<Icon icon={task.icon.ManageTemplates} size={'small'} />
|
||||
<span><Label label={plugin.string.ProcessStates} /></span>
|
||||
<ButtonIcon kind={'primary'} icon={IconAdd} size={'small'} on:click={handleAddStatus} />
|
||||
</div>
|
||||
<StatesProjectEditor
|
||||
{taskType}
|
||||
type={spaceType}
|
||||
{states}
|
||||
on:delete={async (evt) => {
|
||||
if (taskType === undefined) {
|
||||
return
|
||||
}
|
||||
const index = taskType.statuses.findIndex((p) => p === evt.detail.state._id)
|
||||
taskType.statuses.splice(index, 1)
|
||||
await client.update(taskType, {
|
||||
statuses: taskType.statuses
|
||||
})
|
||||
await client.update(spaceType, {
|
||||
statuses: calculateStatuses(spaceType, $taskTypeStore, [
|
||||
{ taskTypeId: taskType._id, statuses: taskType.statuses }
|
||||
])
|
||||
})
|
||||
}}
|
||||
on:move={async (evt) => {
|
||||
if (taskType === undefined) {
|
||||
return
|
||||
}
|
||||
const index = taskType.statuses.findIndex((p) => p === evt.detail.stateID)
|
||||
const state = taskType.statuses.splice(index, 1)[0]
|
||||
|
||||
const statuses = [
|
||||
...taskType.statuses.slice(0, evt.detail.position),
|
||||
state,
|
||||
...taskType.statuses.slice(evt.detail.position)
|
||||
]
|
||||
await client.update(taskType, {
|
||||
statuses
|
||||
})
|
||||
|
||||
await client.update(spaceType, {
|
||||
statuses: calculateStatuses(spaceType, $taskTypeStore, [{ taskTypeId: taskType._id, statuses }])
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ClassAttributes ofClass={taskType.ofClass} _class={taskType.targetClass} showHierarchy />
|
||||
</div>
|
||||
</Scroller>
|
||||
</div>
|
||||
<ModernButton
|
||||
icon={IconSquareExpand}
|
||||
label={plugin.string.CountTasks}
|
||||
labelParams={{ count: taskTypeCounter.get(taskType._id) ?? 0 }}
|
||||
disabled={taskTypeCounter.get(taskType._id) === undefined}
|
||||
kind={'tertiary'}
|
||||
size={'medium'}
|
||||
hasMenu
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AttributeEditor _class={task.class.TaskType} object={taskType} key="name" editKind={'modern-ghost-large'} />
|
||||
|
||||
<div class="flex-row-center mt-4 ml-4 mr-4 gap-4">
|
||||
<div class="flex-no-shrink trans-title uppercase">
|
||||
<Label label={getEmbeddedLabel('Parent type restrictions')} />
|
||||
</div>
|
||||
{#if taskType.kind === 'subtask' || taskType.kind === 'both'}
|
||||
<TaskTypeRefEditor
|
||||
label={getEmbeddedLabel('Allowed parents')}
|
||||
value={taskType.allowedAsChildOf}
|
||||
types={taskTypes.filter((it) => it.kind === 'task' || it.kind === 'both')}
|
||||
onChange={(evt) => {
|
||||
void client.diffUpdate(taskType, { allowedAsChildOf: evt })
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hulyTableAttr-container">
|
||||
<div class="hulyTableAttr-header font-medium-12">
|
||||
<Icon icon={task.icon.ManageTemplates} size={'small'} />
|
||||
<span><Label label={plugin.string.ProcessStates} /></span>
|
||||
<ButtonIcon kind={'primary'} icon={IconAdd} size={'small'} on:click={handleAddStatus} />
|
||||
</div>
|
||||
<StatesProjectEditor
|
||||
{taskType}
|
||||
type={projectType}
|
||||
{states}
|
||||
on:delete={async (evt) => {
|
||||
const index = taskType.statuses.findIndex((p) => p === evt.detail.state._id)
|
||||
taskType.statuses.splice(index, 1)
|
||||
await client.update(taskType, {
|
||||
statuses: taskType.statuses
|
||||
})
|
||||
await client.update(projectType, {
|
||||
statuses: calculateStatuses(projectType, $taskTypeStore, [
|
||||
{ taskTypeId: taskType._id, statuses: taskType.statuses }
|
||||
])
|
||||
})
|
||||
}}
|
||||
on:move={async (evt) => {
|
||||
const index = taskType.statuses.findIndex((p) => p === evt.detail.stateID)
|
||||
const state = taskType.statuses.splice(index, 1)[0]
|
||||
|
||||
const statuses = [
|
||||
...taskType.statuses.slice(0, evt.detail.position),
|
||||
state,
|
||||
...taskType.statuses.slice(evt.detail.position)
|
||||
]
|
||||
await client.update(taskType, {
|
||||
statuses
|
||||
})
|
||||
|
||||
await client.update(projectType, {
|
||||
statuses: calculateStatuses(projectType, $taskTypeStore, [{ taskTypeId: taskType._id, statuses }])
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ClassAttributes ofClass={taskType.ofClass} _class={taskType.targetClass} showHierarchy />
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user