Allow to create new team (#2375)

Signed-off-by: muhtimur <timur.mukhamedishin@xored.com>
This commit is contained in:
Timur Mukhamedishin 2022-11-17 11:31:42 +07:00 committed by GitHub
parent 7e03e8f59d
commit 2cd894e7ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 233 additions and 32 deletions

View File

@ -679,6 +679,10 @@ export function createModel (builder: Builder): void {
presenter: tracker.component.ProjectTitlePresenter
})
builder.mixin(tracker.class.Team, core.class.Class, view.mixin.AttributePresenter, {
presenter: tracker.component.TeamPresenter
})
classPresenter(
builder,
tracker.class.Project,

View File

@ -50,6 +50,9 @@
<path d="M4.92886 7.49494C4.99698 7.22733 5.21443 7 5.49057 7C5.76671 7 5.98416 7.22733 6.05228 7.49494C6.27254 8.36012 7.05683 9 7.99057 9C8.92431 9 9.7086 8.36012 9.92886 7.49494C9.99698 7.22733 10.2144 7 10.4906 7C10.7667 7 10.9842 7.22733 11.0523 7.49494C11.2725 8.36012 12.0568 9 12.9906 9C14.0154 9 14.8602 8.22917 14.9768 7.23566C14.9949 7.08147 14.9544 6.92771 14.885 6.78885L12.267 1.55279C12.0976 1.214 11.7513 1 11.3725 1H4.6086C4.22983 1 3.88357 1.214 3.71418 1.55279L1.09614 6.78885C1.02672 6.92771 0.98621 7.08147 1.00431 7.23566C1.12092 8.22917 1.96573 9 2.99057 9C3.92431 9 4.7086 8.36012 4.92886 7.49494Z"/>
<path d="M3.01079 10C4.05518 10 4.97364 9.46684 5.51079 8.65844C6.04793 9.46684 6.9664 10 8.01079 10C9.05518 10 9.97365 9.46684 10.5108 8.65844C11.0479 9.46684 11.9664 10 13.0108 10C13.3538 10 13.6833 9.94247 13.9902 9.83653V13.5C13.9902 14.3284 13.3187 15 12.4902 15H10.5C10.2239 15 10 14.7761 10 14.5V13C10 12.4477 9.55229 12 9 12H7C6.44772 12 6 12.4477 6 13V14.5C6 14.7761 5.77614 15 5.5 15H3.49023C2.66181 15 1.99023 14.3284 1.99023 13.5V9.82201C2.30876 9.9372 2.6524 10 3.01079 10Z"/>
</symbol>
<symbol id="red-circle" viewBox="0 0 16 16" fill="#ff324cee">
<path d="M7,0C3.1,0,0,3.1,0,7c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7C14,3.1,10.9,0,7,0z M7,12c-2.8,0-5-2.2-5-5s2.2-5,5-5s5,2.2,5,5S9.8,12,7,12z" />
</symbol>
<symbol id="status-backlog" viewBox="0 0 14 14">
<path d="M13.9,7.9l-2-0.3c0-0.2,0-0.4,0-0.7s0-0.4,0-0.7l2-0.3C14,6.4,14,6.7,14,7S14,7.6,13.9,7.9z M13.5,4.3c-0.2-0.6-0.5-1.1-0.9-1.6L11,4c0.3,0.3,0.5,0.7,0.7,1.1L13.5,4.3z M11.3,1.4L10,3C9.7,2.8,9.3,2.5,8.9,2.4l0.8-1.8C10.2,0.8,10.8,1.1,11.3,1.4z M7.9,0.1L7.7,2C7.4,2,7.2,2,7,2S6.6,2,6.3,2l-0.3-2C6.4,0,6.7,0,7,0S7.6,0,7.9,0.1z M4.3,0.5l0.8,1.8C4.7,2.5,4.3,2.8,4,3L2.7,1.4C3.2,1.1,3.8,0.8,4.3,0.5z M1.4,2.7L3,4C2.8,4.3,2.5,4.7,2.4,5.1L0.5,4.3C0.8,3.8,1.1,3.2,1.4,2.7z M0.1,6.1C0,6.4,0,6.7,0,7s0,0.6,0.1,0.9l2-0.3C2,7.4,2,7.2,2,7s0-0.4,0-0.7L0.1,6.1z M0.5,9.7l1.8-0.8C2.5,9.3,2.8,9.7,3,10l-1.6,1.2C1.1,10.8,0.8,10.2,0.5,9.7z M2.7,12.6L4,11c0.3,0.3,0.7,0.5,1.1,0.7l-0.8,1.8C3.8,13.2,3.2,12.9,2.7,12.6z M6.1,13.9l0.3-2c0.2,0,0.4,0,0.7,0s0.4,0,0.7,0l0.3,2C7.6,14,7.3,14,7,14S6.4,14,6.1,13.9z M9.7,13.5l-0.8-1.8c0.4-0.2,0.8-0.4,1.1-0.7l1.2,1.6C10.8,12.9,10.2,13.2,9.7,13.5z M12.6,11.3L11,10c0.3-0.3,0.5-0.7,0.7-1.1l1.8,0.8C13.2,10.2,12.9,10.8,12.6,11.3z" />

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -40,6 +40,11 @@
"Completed": "Completed",
"Canceled": "Canceled",
"CreateTeam": "Create team",
"NewTeam": "New team",
"TeamTitlePlaceholder": "Team title",
"MakePrivate": "Make private",
"MakePrivateDescription": "Only members can see it",
"ChooseIcon": "Choose icon",
"AddIssue": "Add Issue",
"NewIssue": "New issue",
"ResumeDraft": "Resume draft",

View File

@ -40,6 +40,11 @@
"Completed": "Завершен",
"Canceled": "Отменено",
"CreateTeam": "Создать команду",
"NewTeam": "Новая команда",
"TeamTitlePlaceholder": "Название команды",
"MakePrivate": "Сделать личным",
"MakePrivateDescription": "Только пользователи могут видеть это",
"ChooseIcon": "Выбрать иконку",
"AddIssue": "Добавить задачу",
"NewIssue": "Новая задача",
"ResumeDraft": "Восстановить черновик",

View File

@ -31,6 +31,7 @@ loadMetadata(tracker.icon, {
NewIssue: `${icons}#newissue`,
Magnifier: `${icons}#magnifier`,
Home: `${icons}#home`,
RedCircle: `${icons}#red-circle`,
Labels: `${icons}#priority-nopriority`, // TODO: add icon
DueDate: `${icons}#inbox`, // TODO: add icon
Parent: `${icons}#myissues`, // TODO: add icon

View File

@ -55,6 +55,7 @@
"@hcengineering/attachment-resources": "~0.6.0",
"@hcengineering/workbench": "~0.6.2",
"@hcengineering/attachment": "~0.6.1",
"@hcengineering/chunter-resources": "~0.6.0"
"@hcengineering/chunter-resources": "~0.6.0",
"@hcengineering/workbench-resources": "~0.6.1"
}
}

View File

@ -0,0 +1,84 @@
<!--
// Copyright © 2022 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 { createEventDispatcher } from 'svelte'
import { Button, EditBox, eventToHTMLElement, Label, showPopup, ToggleWithLabel } from '@hcengineering/ui'
import { getClient, SpaceCreateCard } from '@hcengineering/presentation'
import core, { getCurrentAccount, Ref } from '@hcengineering/core'
import { IssueStatus } from '@hcengineering/tracker'
import { StyledTextBox } from '@hcengineering/text-editor'
import { Asset } from '@hcengineering/platform'
import tracker from '../../plugin'
import TeamIconChooser from './TeamIconChooser.svelte'
let name: string = ''
let description: string = ''
let isPrivate: boolean = false
let icon: Asset | undefined = undefined
const dispatch = createEventDispatcher()
const client = getClient()
async function createTeam () {
await client.createDoc(tracker.class.Team, core.space.Space, {
name,
description,
private: isPrivate,
members: [getCurrentAccount()._id],
archived: false,
identifier: name.toUpperCase().replaceAll(' ', '_'),
sequence: 0,
issueStatuses: 0,
defaultIssueStatus: '' as Ref<IssueStatus>,
icon
})
}
function chooseIcon (ev: MouseEvent) {
showPopup(TeamIconChooser, { icon }, eventToHTMLElement(ev), (result) => {
if (result !== undefined && result !== null) {
icon = result
}
})
}
</script>
<SpaceCreateCard
label={tracker.string.NewTeam}
okAction={createTeam}
canSave={name.length > 0}
on:close={() => {
dispatch('close')
}}
>
<EditBox bind:value={name} placeholder={tracker.string.TeamTitlePlaceholder} kind={'large-style'} focus />
<StyledTextBox
alwaysEdit
showButtons={false}
bind:content={description}
placeholder={tracker.string.IssueDescriptionPlaceholder}
/>
<ToggleWithLabel
label={tracker.string.MakePrivate}
description={tracker.string.MakePrivateDescription}
bind:on={isPrivate}
/>
<div class="flex-between">
<div class="caption">
<Label label={tracker.string.ChooseIcon} />
</div>
<Button icon={icon ?? tracker.icon.Home} kind="no-border" size="medium" on:click={chooseIcon} />
</div>
</SpaceCreateCard>

View File

@ -0,0 +1,41 @@
<script lang="ts">
import { Metadata } from '@hcengineering/platform'
import presentation, { Card } from '@hcengineering/presentation'
import { Button } from '@hcengineering/ui'
import tracker from '../../plugin'
import { createEventDispatcher } from 'svelte'
export let icon: Metadata<string> | undefined = undefined
const dispatch = createEventDispatcher()
const icons = [tracker.icon.Home, tracker.icon.RedCircle]
function save () {
dispatch('close', icon)
}
</script>
<Card
label={tracker.string.ChooseIcon}
okLabel={presentation.string.Save}
okAction={save}
canSave={icon !== undefined}
on:close={() => {
dispatch('close')
}}
>
<div class="float-left-box">
{#each icons as obj}
<div class="float-left p-2">
<Button
icon={obj}
size="medium"
kind={obj === icon ? 'primary' : 'transparent'}
on:click={() => {
icon = obj
}}
/>
</div>
{/each}
</div>
</Card>

View File

@ -0,0 +1,47 @@
<!--
// Copyright © 2022 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, Space } from '@hcengineering/core'
import { Team } from '@hcengineering/tracker'
import { SpacesNavModel } from '@hcengineering/workbench'
import { TreeNode, SpecialElement } from '@hcengineering/workbench-resources'
import { createEventDispatcher } from 'svelte'
export let space: Team
export let model: SpacesNavModel
export let currentSpace: Ref<Space> | undefined
export let currentSpecial: string | undefined
export let selectSpace: Function
export let getActions: Function
const dispatch = createEventDispatcher()
</script>
{#if model.specials}
<TreeNode icon={space?.icon ?? model.icon} title={space.name} indent={'ml-2'} actions={() => getActions(space)}>
{#each model.specials as special}
<SpecialElement
indent={'ml-4'}
label={special.label}
icon={special.icon}
on:click={() => dispatch('special', special.id)}
selected={currentSpace === space._id && special.id === currentSpecial}
on:click={() => {
selectSpace(space._id, special.id)
}}
/>
{/each}
</TreeNode>
{/if}

View File

@ -94,6 +94,9 @@ import IssueTemplates from './components/templates/IssueTemplates.svelte'
import EditIssueTemplate from './components/templates/EditIssueTemplate.svelte'
import TemplateEstimationEditor from './components/templates/EstimationEditor.svelte'
import CreateTeam from './components/teams/CreateTeam.svelte'
import TeamPresenter from './components/teams/TeamPresenter.svelte'
export async function queryIssue<D extends Issue> (
_class: Ref<Class<D>>,
client: Client,
@ -216,7 +219,9 @@ export default async (): Promise<Resources> => ({
IssueTemplates,
IssueTemplatePresenter,
EditIssueTemplate,
TemplateEstimationEditor
TemplateEstimationEditor,
CreateTeam,
TeamPresenter
},
completion: {
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>

View File

@ -60,6 +60,11 @@ export default mergeIds(trackerId, tracker, {
Completed: '' as IntlString,
Canceled: '' as IntlString,
CreateTeam: '' as IntlString,
NewTeam: '' as IntlString,
TeamTitlePlaceholder: '' as IntlString,
MakePrivate: '' as IntlString,
MakePrivateDescription: '' as IntlString,
ChooseIcon: '' as IntlString,
AddIssue: '' as IntlString,
NewIssue: '' as IntlString,
ResumeDraft: '' as IntlString,
@ -273,6 +278,7 @@ export default mergeIds(trackerId, tracker, {
DueDatePresenter: '' as AnyComponent,
EditIssueTemplate: '' as AnyComponent,
CreateTeam: '' as AnyComponent,
TeamPresenter: '' as AnyComponent,
NewIssueHeader: '' as AnyComponent,
IconPresenter: '' as AnyComponent,
LeadPresenter: '' as AnyComponent,

View File

@ -59,11 +59,11 @@ export interface IssueStatusCategory extends Doc {
* @public
*/
export interface Team extends Space {
teamLogo?: string | null
identifier: string // Team identifier
sequence: number
issueStatuses: number
defaultIssueStatus: Ref<IssueStatus>
icon?: Asset
}
/**
@ -413,6 +413,7 @@ export default plugin(trackerId, {
NewIssue: '' as Asset,
Magnifier: '' as Asset,
Home: '' as Asset,
RedCircle: '' as Asset,
Labels: '' as Asset,
DueDate: '' as Asset,
Parent: '' as Asset,

View File

@ -21,12 +21,11 @@
import preference from '@hcengineering/preference'
import { getClient } from '@hcengineering/presentation'
import { Action, getCurrentLocation, IconAdd, IconEdit, IconSearch, navigate, showPopup } from '@hcengineering/ui'
import { getActions as getContributedActions } from '@hcengineering/view-resources'
import { getActions as getContributedActions, getObjectPresenter } from '@hcengineering/view-resources'
import { SpacesNavModel } from '@hcengineering/workbench'
import { createEventDispatcher } from 'svelte'
import plugin from '../../plugin'
import { classIcon, getSpaceName } from '../../utils'
import SpecialElement from './SpecialElement.svelte'
import TreeItem from './TreeItem.svelte'
import TreeNode from './TreeNode.svelte'
@ -113,36 +112,33 @@
<TreeNode label={model.label} parent actions={async () => getParentActions()} indent={'ml-2'}>
{#each spaces as space (space._id)}
{#if model.specials}
<TreeNode icon={model.icon} title={space.name} indent={'ml-2'} actions={() => getActions(space)}>
{#each model.specials as special}
<SpecialElement
{#await getObjectPresenter(client, space._class, { key: '' }) then presenter}
{#if model.specials && presenter}
<svelte:component
this={presenter.presenter}
{space}
{model}
{currentSpace}
{currentSpecial}
{getActions}
{selectSpace}
/>
{:else}
{#await getSpaceName(client, space) then name}
<TreeItem
indent={'ml-4'}
label={special.label}
icon={special.icon}
on:click={() => dispatch('special', special.id)}
selected={currentSpace === space._id && special.id === currentSpecial}
_id={space._id}
title={name}
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
on:click={() => {
selectSpace(space._id, special.id)
selectSpace(space._id)
}}
/>
{/each}
</TreeNode>
{:else}
{#await getSpaceName(client, space) then name}
<TreeItem
indent={'ml-4'}
_id={space._id}
title={name}
icon={classIcon(client, space._class)}
selected={currentSpace === space._id}
actions={() => getActions(space)}
bold={isChanged(space, $lastViews)}
on:click={() => {
selectSpace(space._id)
}}
/>
{/await}
{/if}
{/await}
{/if}
{/await}
{/each}
</TreeNode>

View File

@ -27,6 +27,8 @@ function hasArchiveSpaces (spaces: Space[]): boolean {
return spaces.find((sp) => sp.archived) !== undefined
}
export { default as SpaceBrowser } from './components/SpaceBrowser.svelte'
export { default as TreeNode } from './components/navigator/TreeNode.svelte'
export { default as SpecialElement } from './components/navigator/SpecialElement.svelte'
export default async (): Promise<Resources> => ({
component: {
WorkbenchApp,