Updated layout of Project Types (#4345)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2024-01-12 09:57:34 +03:00 committed by GitHub
parent 262c2dd82c
commit 3ac0e6e259
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1066 additions and 963 deletions

View File

@ -521,7 +521,7 @@ export function createModel (builder: Builder): void {
{ {
name: board.string.BoardApplication, name: board.string.BoardApplication,
description: board.string.ManageBoardStatuses, description: board.string.ManageBoardStatuses,
icon: board.component.TemplatesIcon, icon: board.icon.Board,
baseClass: board.class.Board baseClass: board.class.Board
}, },
board.descriptors.BoardType board.descriptors.BoardType

View File

@ -703,7 +703,7 @@ export function createModel (builder: Builder): void {
{ {
name: lead.string.LeadApplication, name: lead.string.LeadApplication,
description: lead.string.ManageFunnelStatuses, description: lead.string.ManageFunnelStatuses,
icon: lead.component.TemplatesIcon, icon: lead.icon.LeadApplication,
baseClass: lead.class.Funnel baseClass: lead.class.Funnel
}, },
lead.descriptors.FunnelType lead.descriptors.FunnelType

View File

@ -1774,7 +1774,7 @@ export function createModel (builder: Builder): void {
{ {
name: recruit.string.RecruitApplication, name: recruit.string.RecruitApplication,
description: recruit.string.ManageVacancyStatuses, description: recruit.string.ManageVacancyStatuses,
icon: recruit.component.TemplatesIcon, icon: recruit.icon.RecruitApplication,
editor: recruit.component.VacancyTemplateEditor, editor: recruit.component.VacancyTemplateEditor,
baseClass: recruit.class.Vacancy baseClass: recruit.class.Vacancy
}, },

View File

@ -248,7 +248,7 @@ export class TTaskType extends TDoc implements TaskType {
export class TProjectTypeDescriptor extends TDoc implements ProjectTypeDescriptor { export class TProjectTypeDescriptor extends TDoc implements ProjectTypeDescriptor {
name!: IntlString name!: IntlString
description!: IntlString description!: IntlString
icon!: AnyComponent icon!: Asset
editor?: AnyComponent editor?: AnyComponent
baseClass!: Ref<Class<Task>> baseClass!: Ref<Class<Task>>
} }

View File

@ -672,7 +672,7 @@ export function createModel (builder: Builder): void {
{ {
name: tracker.string.TrackerApplication, name: tracker.string.TrackerApplication,
description: tracker.string.ManageWorkflowStatuses, description: tracker.string.ManageWorkflowStatuses,
icon: task.component.TemplatesIcon, icon: task.icon.Task,
baseClass: tracker.class.Project baseClass: tracker.class.Project
}, },
tracker.descriptors.ProjectType tracker.descriptors.ProjectType

View File

@ -267,6 +267,7 @@ input.search {
.justify-stretch { justify-content: stretch; } .justify-stretch { justify-content: stretch; }
.items-baseline { align-items: baseline; } .items-baseline { align-items: baseline; }
.items-start { align-items: flex-start; } .items-start { align-items: flex-start; }
.items-end { align-items: flex-end; }
.items-center { align-items: center; } .items-center { align-items: center; }
.items-stretch { align-items: stretch; } .items-stretch { align-items: stretch; }
.self-start { align-self: flex-start; } .self-start { align-self: flex-start; }

View File

@ -1,16 +1,6 @@
// //
// Copyright © 2021 Anticrm Platform Contributors. // © 2023 Hardcore Engineering, Inc. All Rights Reserved.
// // Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
// 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.
// //
/* Common Colors */ /* Common Colors */
@ -37,6 +27,7 @@
/* Dark Theme */ /* Dark Theme */
.theme-dark { .theme-dark {
--global-ui-BackgroundColor: #A5BDFF0D; --global-ui-BackgroundColor: #A5BDFF0D;
--global-ui-BorderColor: #A5BDFF1A;
--global-ui-hover-BackgroundColor: #A5BDFF1A; --global-ui-hover-BackgroundColor: #A5BDFF1A;
--global-ui-highlight-BackgroundColor: #A5BDFF0D; --global-ui-highlight-BackgroundColor: #A5BDFF0D;
--global-ui-hover-highlight-BackgroundColor: #A5BDFF26; --global-ui-hover-highlight-BackgroundColor: #A5BDFF26;
@ -53,6 +44,7 @@
--global-primary-TextColor: #FFFFFF; --global-primary-TextColor: #FFFFFF;
--global-secondary-TextColor: #C1C9D6; --global-secondary-TextColor: #C1C9D6;
--global-tertiary-TextColor: #8E99AF; --global-tertiary-TextColor: #8E99AF;
--global-disabled-TextColor: #5A667E;
--global-accent-TextColor: #4D7FF5; --global-accent-TextColor: #4D7FF5;
--global-focus-BorderColor: #2A59D6; --global-focus-BorderColor: #2A59D6;
@ -88,6 +80,7 @@
/* Light Theme */ /* Light Theme */
.theme-light { .theme-light {
--global-ui-BackgroundColor: #1530720D; --global-ui-BackgroundColor: #1530720D;
--global-ui-BorderColor: #1530721A;
--global-ui-hover-BackgroundColor: #1530721A; --global-ui-hover-BackgroundColor: #1530721A;
--global-ui-highlight-BackgroundColor: #A5BDFF26; --global-ui-highlight-BackgroundColor: #A5BDFF26;
--global-ui-hover-highlight-BackgroundColor: #A5BDFF40; --global-ui-hover-highlight-BackgroundColor: #A5BDFF40;
@ -104,6 +97,7 @@
--global-primary-TextColor: #0F121A; --global-primary-TextColor: #0F121A;
--global-secondary-TextColor: #5A667E; --global-secondary-TextColor: #5A667E;
--global-tertiary-TextColor: #7B879E; --global-tertiary-TextColor: #7B879E;
--global-disabled-TextColor: #A1ABBF;
--global-accent-TextColor: #3566E2; --global-accent-TextColor: #3566E2;
--global-focus-BorderColor: #204DC8; --global-focus-BorderColor: #204DC8;

View File

@ -28,7 +28,8 @@
.hulyComponent-content, .hulyComponent-content,
.hulyComponent-content__container, .hulyComponent-content__container,
.hulyComponent-content__column, .hulyComponent-content__column,
.hulyComponent-content__column.content, .hulyComponent-content__column-group,
.hulyComponent-content__header,
.hulyComponent-content__navHeader { .hulyComponent-content__navHeader {
display: flex; display: flex;
width: 100%; width: 100%;
@ -39,6 +40,9 @@
flex-shrink: 0; flex-shrink: 0;
max-width: 64rem; max-width: 64rem;
&.gap {
gap: var(--spacing-4);
}
&__container { &__container {
height: 100%; height: 100%;
} }
@ -54,18 +58,21 @@
margin: 0 0.75rem; margin: 0 0.75rem;
} }
&.content { &.content {
overflow-y: auto; align-items: stretch;
}
&-group {
flex-direction: column; flex-direction: column;
align-items: center; flex-shrink: 0;
padding: var(--spacing-3); height: fit-content;
} }
} }
&__navHeader { &__navHeader {
flex-direction: column; flex-direction: column;
flex-shrink: 0; flex-shrink: 0;
padding-bottom: var(--spacing-3);
border-bottom: 1px solid var(--theme-navpanel-divider);
&.divide {
border-bottom: 1px solid var(--theme-navpanel-divider);
}
&-menu { &-menu {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -76,9 +83,18 @@
height: var(--global-extra-large-Size); height: var(--global-extra-large-Size);
} }
&-hint { &-hint {
margin: var(--spacing-0_25) var(--spacing-3) 0 var(--spacing-2); margin: var(--spacing-0_25) var(--spacing-3) var(--spacing-3) var(--spacing-2);
} }
} }
&__header {
justify-content: space-between;
align-self: stretch;
padding: 0 0 var(--spacing-1) var(--spacing-1_5);
}
textarea {
font-weight: 400 !important;
color: var(--global-tertiary-TextColor) !important;
}
} }
/* Header */ /* Header */
@ -123,8 +139,17 @@
gap: var(--spacing-0_5); gap: var(--spacing-0_5);
} }
.hulyHeader-buttonsGroup { .hulyHeader-buttonsGroup {
flex-shrink: 0;
gap: var(--spacing-1); gap: var(--spacing-1);
margin-left: var(--spacing-2); margin-left: var(--spacing-2);
&__label {
display: flex;
flex-direction: column;
align-items: flex-end;
flex-shrink: 0;
color: var(--global-secondary-TextColor);
}
} }
.hulyHotKey-item { .hulyHotKey-item {
margin-right: .625rem; margin-right: .625rem;

View File

@ -26,6 +26,7 @@
@import "./mixins.scss"; @import "./mixins.scss";
@import "./panel.scss"; @import "./panel.scss";
@import "./prose.scss"; @import "./prose.scss";
@import "./tables.scss";
@import "./_text-editor.scss"; @import "./_text-editor.scss";
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;1,400;1,500&display=swap'); @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;1,400;1,500&display=swap');

View File

@ -0,0 +1,240 @@
//
// © 2024 Hardcore Engineering, Inc. All Rights Reserved.
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
//
/* Huly Attribute Table */
.hulyTableAttr-container {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
background-color: var(--theme-table-row-color);
border: 1px solid var(--theme-divider-color);
border-radius: var(--large-BorderRadius);
.hulyTableAttr-header {
display: flex;
justify-content: space-between;
align-items: center;
align-self: stretch;
flex-shrink: 0;
text-transform: uppercase;
color: var(--global-secondary-TextColor);
&.withButton {
padding: var(--spacing-2);
}
&:not(.withButton) {
padding: var(--spacing-2) var(--spacing-2) var(--spacing-2) var(--spacing-2_5);
}
span {
flex-grow: 1;
margin-left: var(--spacing-1_5);
}
}
.hulyTableAttr-content {
display: flex;
align-items: flex-start;
align-self: stretch;
flex-shrink: 0;
min-width: 0;
min-height: 0;
border-top: 1px solid var(--theme-divider-color);
&:not(.withTitle) {
flex-direction: column;
}
&.withTitle {
gap: var(--spacing-1);
.hulyTableAttr-content__title {
display: flex;
align-items: flex-start;
align-self: stretch;
gap: 8px;
padding: var(--spacing-1_5);
min-width: 8.75rem;
max-width: 8.75rem;
text-transform: uppercase;
font-size: .75rem;
font-weight: 500;
line-height: 1rem;
color: var(--global-secondary-TextColor);
}
.hulyTableAttr-content__wrapper {
display: flex;
flex-direction: column;
flex-grow: 1;
flex-shrink: 1;
height: fit-content;
min-width: 0;
min-height: 0;
}
}
&__row {
display: flex;
align-items: center;
align-self: stretch;
flex-grow: 1;
margin: 0;
min-width: 0;
border-radius: var(--small-BorderRadius);
border: none;
outline: none;
&-dragMenu,
&-icon-wrapper {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
&-dragMenu {
margin: 0;
padding: 0;
width: var(--global-extra-small-Size);
height: var(--global-extra-small-Size);
color: var(--button-disabled-IconColor);
border-radius: var(--extra-small-BorderRadius);
border: none;
outline: none;
}
&-icon {
width: var(--global-min-Size);
height: var(--global-min-Size);
color: var(--global-primary-TextColor);
}
&-icon-wrapper {
width: var(--global-medium-Size);
height: var(--global-medium-Size);
color: var(--global-secondary-TextColor);
background-color: var(--theme-panel-color); // var(--global-surface-02-BackgroundColor);
border-radius: var(--small-BorderRadius);
&.pointer {
cursor: pointer;
}
}
&-labels-group {
display: flex;
flex-direction: column;
flex-grow: 1;
gap: var(--spacing-0_25);
min-width: 0;
min-height: 0;
}
&-label {
overflow: hidden;
white-space: nowrap;
word-break: break-all;
text-overflow: ellipsis;
text-align: left;
flex-shrink: 1;
min-width: 0;
color: var(--global-primary-TextColor);
&.grow {
flex-grow: 1;
}
&.accent {
font-weight: 500;
}
&.dark {
color: var(--global-secondary-TextColor);
}
p {
color: inherit;
}
}
&-type {
white-space: nowrap;
text-transform: uppercase;
color: var(--global-secondary-TextColor);
}
&-arrow {
display: none;
flex-shrink: 0;
width: var(--global-min-Size);
height: var(--global-min-Size);
color: var(--global-primary-LinkColor);
}
}
&.class .hulyTableAttr-content__row,
&.task .hulyTableAttr-content__row {
&.hovered,
&:hover {
background-color: var(--theme-table-header-color); // var(--global-surface-03-hover-BackgroundColor);
}
&.selected {
background-color: var(--theme-table-header-color); // var(--global-surface-03-hover-BackgroundColor);
.hulyTableAttr-content__row-icon,
.hulyTableAttr-content__row-arrow,
.hulyTableAttr-content__row-label {
color: var(--global-primary-LinkColor);
}
.hulyTableAttr-content__row-type {
color: var(--global-primary-TextColor);
}
.hulyTableAttr-content__row-label {
font-weight: 700;
}
}
}
&.class {
padding: var(--spacing-1);
.hulyTableAttr-content__row {
gap: var(--spacing-1);
padding: var(--spacing-1) var(--spacing-2) var(--spacing-1) var(--spacing-1);
&.hovered .hulyTableAttr-content__row-arrow,
&:hover .hulyTableAttr-content__row-arrow,
&.selected .hulyTableAttr-content__row-arrow {
display: block;
}
}
}
&.task {
.hulyTableAttr-content__row {
gap: var(--spacing-1);
padding: var(--spacing-1_5);
border-radius: 0;
&:last-child {
border-radius: 0 0 var(--large-BorderRadius) var(--large-BorderRadius);
}
.hulyTableAttr-content__row-icon-wrapper {
margin-right: var(--spacing-0_5);
}
}
.hulyTableAttr-content__row + .hulyTableAttr-content__row {
border-top: 1px solid var(--theme-divider-color);
}
}
&.automation {
.hulyTableAttr-content__row {
gap: var(--spacing-2);
padding: var(--spacing-1_5) var(--spacing-1_5) var(--spacing-1_5) var(--spacing-2_5);
border-radius: 0;
cursor: default;
&:last-child {
border-radius: 0 0 var(--large-BorderRadius) var(--large-BorderRadius);
}
.hulyTableAttr-content__row-icon-group {
display: flex;
flex-direction: row;
align-items: center;
flex-shrink: 0;
flex-wrap: nowrap;
gap: var(--spacing-1);
}
}
.hulyTableAttr-content__row + .hulyTableAttr-content__row {
border-top: 1px solid var(--theme-divider-color);
}
}
}
}

View File

@ -4,6 +4,8 @@
"Ok": "Ok", "Ok": "Ok",
"Cancel": "Cancel", "Cancel": "Cancel",
"Save": "Save", "Save": "Save",
"Publish": "Publish",
"SaveDraft": "Save draft",
"Minutes": "{minutes, plural, =0 {less than a minute ago} =1 {a minute ago} other {# minutes ago}}", "Minutes": "{minutes, plural, =0 {less than a minute ago} =1 {a minute ago} other {# minutes ago}}",
"Hours": "{hours, plural, =0 {less than an hour ago} =1 {an hour ago} other {# hours ago}}", "Hours": "{hours, plural, =0 {less than an hour ago} =1 {an hour ago} other {# hours ago}}",
"Days": "{days, plural, =0 {today} =1 {yesterday} other {# days ago}}", "Days": "{days, plural, =0 {today} =1 {yesterday} other {# days ago}}",

View File

@ -4,6 +4,8 @@
"Ok": "Ок", "Ok": "Ок",
"Cancel": "Отменить", "Cancel": "Отменить",
"Save": "Сохранить", "Save": "Сохранить",
"Publish": "Опубликовать",
"SaveDraft": "Сохранить черновик",
"Minutes": "{minutes, plural, =0 {меньше минуты назад} =1 {минуту назад} one {# минуту назад} few {# минуты назад} other {# минут назад}}", "Minutes": "{minutes, plural, =0 {меньше минуты назад} =1 {минуту назад} one {# минуту назад} few {# минуты назад} other {# минут назад}}",
"Hours": "{hours, plural, =0 {меньше часа назад} =1 {час назад} one {# час назад} few {# часа назад} other {# часов назад}}", "Hours": "{hours, plural, =0 {меньше часа назад} =1 {час назад} one {# час назад} few {# часа назад} other {# часов назад}}",
"Days": "{days, plural, =0 {сегодня} =1 {вчера} one {# день назад} few {# дня назад} other {# дней назад}}", "Days": "{days, plural, =0 {сегодня} =1 {вчера} one {# день назад} few {# дня назад} other {# дней назад}}",

View File

@ -46,9 +46,10 @@
}} }}
/> />
{/each} {/each}
{#if afterLabel} {#if afterLabel || $$slots.afterLabel}
<span class="hulyBreadcrumbs-afterLabel"> <span class="hulyBreadcrumbs-afterLabel font-medium-12">
<Label label={afterLabel} /> {#if afterLabel}<Label label={afterLabel} />{/if}
<slot name="afterLabel" />
</span> </span>
{/if} {/if}
</div> </div>

View File

@ -22,6 +22,7 @@
export let title: string | undefined = undefined export let title: string | undefined = undefined
export let label: IntlString | undefined = undefined export let label: IntlString | undefined = undefined
export let labelParams: Record<string, any> = {}
export let icon: Asset | AnySvelteComponent | ComponentType | undefined = undefined export let icon: Asset | AnySvelteComponent | ComponentType | undefined = undefined
export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative'
export let size: 'large' | 'medium' | 'small' export let size: 'large' | 'medium' | 'small'
@ -43,10 +44,13 @@
on:click on:click
> >
{#if loading} {#if loading}
<div class="icon animate"><Spinner size={'small'} /></div> <div class="icon animate"><Spinner size={type === 'type-button' && !hasMenu ? 'medium' : 'small'} /></div>
{:else if icon}<div class="icon"><Icon {icon} size={'small'} /></div>{/if} {:else if icon}<div class="icon">
<Icon {icon} size={type === 'type-button' && !hasMenu ? 'medium' : 'small'} />
</div>{/if}
{#if label}<span><Label {label} params={labelParams} /></span>{/if}
{#if title}<span>{title}</span>{/if} {#if title}<span>{title}</span>{/if}
{#if label}<span><Label {label} /></span>{/if} <slot />
</button> </button>
<style lang="scss"> <style lang="scss">
@ -113,7 +117,8 @@
width: var(--spacing-4); width: var(--spacing-4);
} }
} }
&.type-button-icon .icon { &.type-button-icon .icon,
&.menu .icon {
width: var(--spacing-2); width: var(--spacing-2);
height: var(--spacing-2); height: var(--spacing-2);
} }
@ -123,7 +128,7 @@
background-color: var(--button-primary-BackgroundColor); background-color: var(--button-primary-BackgroundColor);
.icon { .icon {
fill: var(--button-accent-IconColor); color: var(--button-accent-IconColor);
} }
span { span {
color: var(--button-accent-LabelColor); color: var(--button-accent-LabelColor);
@ -145,7 +150,7 @@
cursor: not-allowed; cursor: not-allowed;
.icon { .icon {
fill: var(--button-disabled-IconColor); color: var(--button-disabled-IconColor);
} }
span { span {
color: var(--button-disabled-LabelColor); color: var(--button-disabled-LabelColor);
@ -165,7 +170,7 @@
background-color: var(--button-secondary-BackgroundColor); background-color: var(--button-secondary-BackgroundColor);
.icon { .icon {
fill: var(--button-subtle-IconColor); color: var(--button-subtle-IconColor);
} }
span { span {
color: var(--button-subtle-LabelColor); color: var(--button-subtle-LabelColor);
@ -187,7 +192,7 @@
cursor: not-allowed; cursor: not-allowed;
.icon { .icon {
fill: var(--button-disabled-IconColor); color: var(--button-disabled-IconColor);
} }
span { span {
color: var(--button-disabled-LabelColor); color: var(--button-disabled-LabelColor);
@ -207,13 +212,13 @@
background-color: transparent; background-color: transparent;
&:not(.inheritColor) .icon { &:not(.inheritColor) .icon {
fill: var(--button-subtle-IconColor); color: var(--button-subtle-IconColor);
} }
&.inheritColor { &.inheritColor {
color: inherit; color: inherit;
.icon { .icon {
fill: currentColor; color: currentColor;
} }
} }
span { span {
@ -235,7 +240,7 @@
cursor: not-allowed; cursor: not-allowed;
.icon { .icon {
fill: var(--button-disabled-IconColor); color: var(--button-disabled-IconColor);
} }
span { span {
color: var(--button-disabled-LabelColor); color: var(--button-disabled-LabelColor);
@ -255,7 +260,7 @@
background-color: var(--button-negative-BackgroundColor); background-color: var(--button-negative-BackgroundColor);
.icon { .icon {
fill: var(--button-accent-IconColor); color: var(--button-accent-IconColor);
} }
span { span {
color: var(--button-accent-LabelColor); color: var(--button-accent-LabelColor);
@ -277,7 +282,7 @@
cursor: not-allowed; cursor: not-allowed;
.icon { .icon {
fill: var(--button-disabled-IconColor); color: var(--button-disabled-IconColor);
} }
span { span {
color: var(--button-disabled-LabelColor); color: var(--button-disabled-LabelColor);

View File

@ -0,0 +1,37 @@
<script lang="ts">
//
// © 2023 Hardcore Engineering, Inc. All Rights Reserved.
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
//
import type { Asset, IntlString } from '@hcengineering/platform'
import { AnySvelteComponent } from '../types'
import { ComponentType } from 'svelte'
import ButtonBase from './ButtonBase.svelte'
export let title: string | undefined = undefined
export let label: IntlString | undefined = undefined
export let labelParams: Record<string, any> = {}
export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' = 'secondary'
export let size: 'large' | 'medium' | 'small' = 'large'
export let icon: Asset | AnySvelteComponent | ComponentType | undefined = undefined
export let disabled: boolean = false
export let loading: boolean = false
export let hasMenu: boolean = false
</script>
<ButtonBase
type={'type-button'}
{title}
{label}
{labelParams}
{kind}
{size}
{icon}
{loading}
{disabled}
{hasMenu}
on:click
>
<slot />
</ButtonBase>

View File

@ -1,18 +1,10 @@
<!--
// Copyright © 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"> <script lang="ts">
//
// © 2023 Hardcore Engineering, Inc. All Rights Reserved.
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
//
import { createEventDispatcher } from 'svelte'
import { IntlString, translate } from '@hcengineering/platform' import { IntlString, translate } from '@hcengineering/platform'
import Label from './Label.svelte' import Label from './Label.svelte'
import { themeStore } from '..' import { themeStore } from '..'
@ -26,6 +18,8 @@
export let password: boolean = false export let password: boolean = false
export let limit: number = 0 export let limit: number = 0
const dispatch = createEventDispatcher()
$: maxlength = limit === 0 ? null : limit $: maxlength = limit === 0 ? null : limit
let placeholderStr: string = '' let placeholderStr: string = ''
@ -48,10 +42,12 @@
spellcheck="false" spellcheck="false"
{disabled} {disabled}
{maxlength} {maxlength}
on:blur
on:change on:change
on:keyup on:keyup
on:input on:input
on:blur={() => {
dispatch('blur', value)
}}
/> />
{:else} {:else}
<input <input
@ -64,10 +60,12 @@
spellcheck="false" spellcheck="false"
{disabled} {disabled}
{maxlength} {maxlength}
on:blur
on:change on:change
on:keyup on:keyup
on:input on:input
on:blur={() => {
dispatch('blur', value)
}}
/> />
{/if} {/if}
{#if labeled}<div class="font-regular-14 label"><Label {label} /></div>{/if} {#if labeled}<div class="font-regular-14 label"><Label {label} /></div>{/if}

View File

@ -174,6 +174,7 @@
} }
&.type-anchor-link { &.type-anchor-link {
padding: 0 0.75rem 0 0.625rem; padding: 0 0.75rem 0 0.625rem;
width: fit-content;
min-height: 1.75rem; min-height: 1.75rem;
.hulyNavItem-icon, .hulyNavItem-icon,

View File

@ -25,6 +25,7 @@
import IconUpOutline from './icons/UpOutline.svelte' import IconUpOutline from './icons/UpOutline.svelte'
export let padding: string | undefined = undefined export let padding: string | undefined = undefined
export let bottomPadding: string | undefined = undefined
export let autoscroll: boolean = false export let autoscroll: boolean = false
export let bottomStart: boolean = false export let bottomStart: boolean = false
export let fade: FadeOptions = defaultSP export let fade: FadeOptions = defaultSP
@ -32,6 +33,7 @@
export let invertScroll: boolean = false export let invertScroll: boolean = false
export let contentDirection: 'vertical' | 'vertical-reverse' | 'horizontal' = 'vertical' export let contentDirection: 'vertical' | 'vertical-reverse' | 'horizontal' = 'vertical'
export let horizontal: boolean = contentDirection === 'horizontal' export let horizontal: boolean = contentDirection === 'horizontal'
export let align: 'start' | 'center' | 'end' | 'stretch' = 'stretch'
export let gap: string | undefined = undefined export let gap: string | undefined = undefined
export let noStretch: boolean = autoscroll export let noStretch: boolean = autoscroll
export let buttons: 'normal' | 'union' | false = false export let buttons: 'normal' | 'union' | false = false
@ -555,6 +557,7 @@
? 'column-reverse' ? 'column-reverse'
: 'row'} : 'row'}
style:height={contentDirection === 'vertical-reverse' ? 'max-content' : noStretch ? 'auto' : '100%'} style:height={contentDirection === 'vertical-reverse' ? 'max-content' : noStretch ? 'auto' : '100%'}
style:align-items={align}
use:resizeObserver={() => { use:resizeObserver={() => {
checkAutoScroll() checkAutoScroll()
checkFade() checkFade()
@ -565,6 +568,9 @@
> >
{#if bottomStart}<div class="flex-grow flex-shrink" />{/if} {#if bottomStart}<div class="flex-grow flex-shrink" />{/if}
<slot /> <slot />
{#if bottomPadding}
<div style:width={'100%'} style:min-height={bottomPadding} />
{/if}
</div> </div>
</div> </div>
</div> </div>

View File

@ -22,6 +22,7 @@
export let label: IntlString | undefined = undefined export let label: IntlString | undefined = undefined
export let width: string | undefined = undefined export let width: string | undefined = undefined
export let height: string | undefined = undefined export let height: string | undefined = undefined
export let margin: string | undefined = undefined
export let value: string | undefined = undefined export let value: string | undefined = undefined
export let placeholder: IntlString = plugin.string.EditBoxPlaceholder export let placeholder: IntlString = plugin.string.EditBoxPlaceholder
export let placeholderParam: any | undefined = undefined export let placeholderParam: any | undefined = undefined
@ -40,7 +41,7 @@
} }
</script> </script>
<div class="textarea" class:no-focus-border={noFocusBorder} style:width style:height> <div class="textarea" class:no-focus-border={noFocusBorder} style:width style:height style:margin>
{#if label}<div class="label"><Label {label} /></div>{/if} {#if label}<div class="label"><Label {label} /></div>{/if}
<textarea <textarea
bind:value bind:value

View File

@ -15,7 +15,7 @@
--> -->
<script lang="ts"> <script lang="ts">
export let size: 'small' | 'medium' | 'large' export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor' export let fill: string = 'currentColor'
</script> </script>
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> <svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">

View File

@ -0,0 +1,10 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path
d="M27.71 4.29049C27.575 4.15616 27.4045 4.06316 27.2185 4.02241C27.0325 3.98167 26.8388 3.99488 26.66 4.06049L4.66 12.0605C4.47027 12.1325 4.30692 12.2604 4.19165 12.4274C4.07638 12.5944 4.01465 12.7926 4.01465 12.9955C4.01465 13.1984 4.07638 13.3965 4.19165 13.5635C4.30692 13.7305 4.47027 13.8585 4.66 13.9305L14.26 17.7705L18.1 27.3705C18.1721 27.5519 18.2958 27.7082 18.4557 27.8201C18.6157 27.9321 18.8049 27.9948 19 28.0005C19.2021 27.9963 19.3982 27.9311 19.5624 27.8132C19.7266 27.6954 19.8513 27.5306 19.92 27.3405L27.92 5.34049C27.9881 5.16356 28.0046 4.97092 27.9674 4.78501C27.9302 4.5991 27.8409 4.4276 27.71 4.29049ZM19 24.2005L16.21 17.2005L20.295 13.1155C20.6844 12.7261 20.6844 12.0948 20.295 11.7055C19.9056 11.3161 19.2744 11.3161 18.885 11.7055L14.76 15.8305L7.8 13.0005L25.33 6.67049L19 24.2005Z"
/>
</svg>

View File

@ -0,0 +1,13 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large' | 'full'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<path
d="M18 6.00012V4.00012H28V14.0001H26V7.41012L15.7136 17.7008L15.7071 17.7073C15.3166 18.0978 14.6834 18.0978 14.2929 17.7073C13.9024 17.3167 13.9024 16.6836 14.2929 16.293L24.59 6.00012H18Z"
/>
<path
d="M7.8415 6.10109C8.88208 5.96118 9.94455 5.97558 11.0001 5.99059C11.5524 5.99845 12.0001 5.55245 12.0001 5.00017C12.0001 4.44788 11.5524 3.99873 11.0001 3.99235C9.85582 3.97912 8.7048 3.96702 7.57501 4.11892C6.64718 4.24366 5.82775 4.51563 5.17164 5.17174C4.51553 5.82785 4.24357 6.64727 4.11882 7.5751C4 8.45885 4.00003 11.5755 4.00007 12.9296V21.0707C4.00003 22.4248 4 23.5415 4.11882 24.4252C4.24357 25.3531 4.51553 26.1725 5.17164 26.8286C5.82775 27.4847 6.64718 27.7567 7.57501 27.8814C8.45875 28.0002 9.57537 28.0002 10.9295 28.0002H21.0706C22.4247 28.0002 23.5414 28.0002 24.4251 27.8814C25.353 27.7567 26.1724 27.4847 26.8285 26.8286C27.4846 26.1725 27.7566 25.3531 27.8813 24.4252C28.0332 23.2954 28.0211 22.1444 28.0079 21.0001C28.0015 20.4479 27.5524 20.0002 27.0001 20.0002C26.4478 20.0002 26.0018 20.4479 26.0096 21.0001C26.0247 22.0557 26.0391 23.1182 25.8992 24.1587C25.8042 24.8648 25.6369 25.1918 25.4143 25.4144C25.1917 25.637 24.8647 25.8043 24.1586 25.8993C23.4238 25.998 22.4426 26.0002 21.0001 26.0002H11.0001C9.55759 26.0002 8.57632 25.998 7.8415 25.8993C7.13545 25.8043 6.80843 25.637 6.58586 25.4144C6.36329 25.1918 6.19591 24.8648 6.10099 24.1587C6.00219 23.4239 6.00007 22.4426 6.00007 21.0002V13.0002C6.00007 11.5577 6.00219 8.57642 6.10099 7.8416C6.19591 7.13555 6.36329 6.80853 6.58586 6.58595C6.80843 6.36338 7.13545 6.19601 7.8415 6.10109Z"
/>
</svg>

View File

@ -128,6 +128,7 @@ export { default as Header } from './components/Header.svelte'
export { default as Breadcrumb } from './components/Breadcrumb.svelte' export { default as Breadcrumb } from './components/Breadcrumb.svelte'
export { default as Breadcrumbs } from './components/Breadcrumbs.svelte' export { default as Breadcrumbs } from './components/Breadcrumbs.svelte'
export { default as ButtonIcon } from './components/ButtonIcon.svelte' export { default as ButtonIcon } from './components/ButtonIcon.svelte'
export { default as ModernButton } from './components/ModernButton.svelte'
export { default as ModernEditbox } from './components/ModernEditbox.svelte' export { default as ModernEditbox } from './components/ModernEditbox.svelte'
export { default as NavItem } from './components/NavItem.svelte' export { default as NavItem } from './components/NavItem.svelte'
export { default as NavGroup } from './components/NavGroup.svelte' export { default as NavGroup } from './components/NavGroup.svelte'
@ -197,6 +198,8 @@ export { default as IconMinimize } from './components/icons/Minimize.svelte'
export { default as IconChevronRight } from './components/icons/ChevronRight.svelte' export { default as IconChevronRight } from './components/icons/ChevronRight.svelte'
export { default as IconDescription } from './components/icons/Description.svelte' export { default as IconDescription } from './components/icons/Description.svelte'
export { default as IconSettings } from './components/icons/Settings.svelte' export { default as IconSettings } from './components/icons/Settings.svelte'
export { default as IconSend } from './components/icons/Send.svelte'
export { default as IconSquareExpand } from './components/icons/SquareExpand.svelte'
export { default as PanelInstance } from './components/PanelInstance.svelte' export { default as PanelInstance } from './components/PanelInstance.svelte'
export { default as Panel } from './components/Panel.svelte' export { default as Panel } from './components/Panel.svelte'

View File

@ -29,6 +29,8 @@ export const uis = plugin(uiId, {
Ok: '' as IntlString, Ok: '' as IntlString,
Cancel: '' as IntlString, Cancel: '' as IntlString,
Save: '' as IntlString, Save: '' as IntlString,
Publish: '' as IntlString,
SaveDraft: '' as IntlString,
Minutes: '' as IntlString, Minutes: '' as IntlString,
Hours: '' as IntlString, Hours: '' as IntlString,
Days: '' as IntlString, Days: '' as IntlString,

View File

@ -164,4 +164,6 @@ export const settingsSeparators: DefSeparators = [
{ minSize: 17, size: 30, maxSize: 32, float: 'aside' } { minSize: 17, size: 30, maxSize: 32, float: 'aside' }
] ]
export const secondNavSeparators: DefSeparators = [{ minSize: 7, size: 7.5, maxSize: 15, float: 'navigator' }, null]
export const separatorsStore = writable<string[]>([]) export const separatorsStore = writable<string[]>([])

View File

@ -140,8 +140,8 @@
</div> </div>
<Separator name={'notificationSettings'} index={0} color={'var(--theme-divider-color)'} /> <Separator name={'notificationSettings'} index={0} color={'var(--theme-divider-color)'} />
<div class="hulyComponent-content__column content"> <div class="hulyComponent-content__column content">
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content"> <div class="hulyComponent-content">
<Scroller>
{#if group} {#if group}
<NotificationGroupSetting {group} {settings} /> <NotificationGroupSetting {group} {settings} />
{/if} {/if}
@ -150,8 +150,8 @@
<svelte:component this={presenter} /> <svelte:component this={presenter} />
{/await} {/await}
{/if} {/if}
</div>
</Scroller> </Scroller>
</div> </div>
</div> </div>
</div>
</div> </div>

View File

@ -85,6 +85,11 @@
"ConfigurationDisabled": "Disabled", "ConfigurationDisabled": "Disabled",
"ConfigDisable": "Disable", "ConfigDisable": "Disable",
"ConfigEnable": "Enable", "ConfigEnable": "Enable",
"ConfigBeta": "Beta version" "ConfigBeta": "Beta version",
"Properties": "Properties",
"TaskTypes": "Task types",
"Automations": "Automations",
"Collections": "Collections",
"ClassColon": "Class:"
} }
} }

View File

@ -86,6 +86,11 @@
"ConfigurationDisabled": "Выключено", "ConfigurationDisabled": "Выключено",
"ConfigDisable": "Выключить", "ConfigDisable": "Выключить",
"ConfigEnable": "Включить", "ConfigEnable": "Включить",
"ConfigBeta": "Ознакомительная версия" "ConfigBeta": "Ознакомительная версия",
"Properties": "Свойства",
"TaskTypes": "Типы задач",
"Automations": "Автоматизация",
"Collections": "Коллекции",
"ClassColon": "Класс:"
} }
} }

View File

@ -0,0 +1,81 @@
<!--
// 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 core, { AnyAttribute, EnumOf, Type } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { AnySvelteComponent, Icon, IconMoreV2, Label, IconOpenedArrow } from '@hcengineering/ui'
import settings from '../plugin'
export let attribute: AnyAttribute
export let attributeType: IntlString | undefined = undefined
export let selected: boolean = false
export let hovered: boolean = false
export let clickMore: (event: MouseEvent) => Promise<void>
export let attributeMapper:
| {
component: AnySvelteComponent
label: IntlString
props: Record<string, any>
}
| undefined = undefined
const client = getClient()
async function getEnumName (type: Type<any>): Promise<string | undefined> {
const ref = (type as EnumOf).of
const res = await client.findOne(core.class.Enum, { _id: ref })
return res?.name
}
</script>
<button class="hulyTableAttr-content__row" class:hovered class:selected on:contextmenu on:click>
<button class="hulyTableAttr-content__row-dragMenu" on:click|stopPropagation={clickMore}>
<IconMoreV2 size={'small'} />
</button>
{#if attribute.isCustom}
<div class="hulyChip-item font-medium-12">
<Label label={settings.string.Custom} />
</div>
{/if}
{#if attribute.icon !== undefined}
<div class="hulyTableAttr-content__row-icon">
<Icon icon={attribute.icon} size={'small'} />
</div>
{/if}
<div class="hulyTableAttr-content__row-label font-regular-14 grow" class:accent={!attribute.hidden}>
<Label label={attribute.label} />
</div>
{#if attributeMapper}
<svelte:component this={attributeMapper.component} {...attributeMapper.props} {attribute} />
{/if}
<div class="hulyTableAttr-content__row-type font-medium-12">
<Label label={attribute.type.label} />
{#if attributeType !== undefined}
: <Label label={attributeType} />
{/if}
{#if attribute.type._class === core.class.EnumOf}
{#await getEnumName(attribute.type) then name}
{#if name}
: {name}
{/if}
{/await}
{/if}
</div>
<div class="hulyTableAttr-content__row-arrow">
<IconOpenedArrow size={'small'} />
</div>
</button>

View File

@ -13,50 +13,35 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import core, { import core, { AnyAttribute, Class, ClassifierKind, Doc, Ref, Space } from '@hcengineering/core'
AnyAttribute, import { IntlString } from '@hcengineering/platform'
ArrOf, import { createQuery, getClient } from '@hcengineering/presentation'
AttachedDoc,
Class,
ClassifierKind,
Collection,
Doc,
EnumOf,
Ref,
RefTo,
Type
} from '@hcengineering/core'
import { IntlString, getResource } from '@hcengineering/platform'
import presentation, { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
import { import {
Action,
ActionIcon, ActionIcon,
AnySvelteComponent, AnySvelteComponent,
ButtonIcon, ButtonIcon,
Icon,
IconAdd, IconAdd,
IconDelete,
IconEdit, IconEdit,
IconMoreV2,
Label, Label,
Menu,
getEventPositionElement, getEventPositionElement,
showPopup, showPopup,
IconSettings, IconSettings,
IconOpenedArrow ModernButton
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { getContextActions } from '@hcengineering/view-resources' import { ObjectPresenter } from '@hcengineering/view-resources'
import settings from '../plugin' import settings from '../plugin'
import CreateAttribute from './CreateAttribute.svelte' import CreateAttribute from './CreateAttribute.svelte'
import ClassAttributesList from './ClassAttributesList.svelte'
import EditAttribute from './EditAttribute.svelte' import EditAttribute from './EditAttribute.svelte'
import EditClassLabel from './EditClassLabel.svelte' import EditClassLabel from './EditClassLabel.svelte'
import { settingsStore, clearSettingsStore } from '../store' import { settingsStore, clearSettingsStore } from '../store'
import TypesPopup from './typeEditors/TypesPopup.svelte' import TypesPopup from './typeEditors/TypesPopup.svelte'
import { onDestroy } from 'svelte'
export let _class: Ref<Class<Doc>> export let _class: Ref<Class<Doc>>
export let ofClass: Ref<Class<Doc>> | undefined = undefined export let ofClass: Ref<Class<Doc>> | undefined = undefined
export let useOfClassAttributes = true export let showHierarchy: boolean = false
export let showTitle = true export let showTitle: boolean = !showHierarchy
export let attributeMapper: export let attributeMapper:
| { | {
@ -72,139 +57,65 @@
const classQuery = createQuery() const classQuery = createQuery()
let clazz: Class<Doc> | undefined let clazz: Class<Doc> | undefined
let hovered: number | null = null let classes: Class<Doc>[] = []
let selected: number | null = null let clazzHierarchy: Class<Doc<Space>>
let btnAdd: ButtonIcon let selected: AnyAttribute | undefined = undefined
$: classQuery.query(core.class.Class, { _id: _class }, (res) => { $: classQuery.query(core.class.Class, { _id: _class }, (res) => {
clazz = res.shift() clazz = res.shift()
}) })
$: attributes = getCustomAttributes(_class) function hasCustomAttributes (_class: Ref<Class<Doc>>): boolean {
function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] {
const cl = hierarchy.getClass(_class) const cl = hierarchy.getClass(_class)
const attributes = Array.from( const attributes = Array.from(
hierarchy hierarchy.getAllAttributes(_class, _class === ofClass ? core.class.Doc : cl.extends).values()
.getAllAttributes(_class, _class === ofClass && useOfClassAttributes ? core.class.Doc : cl.extends)
.values()
) )
return attributes return attributes.length > 0
}
const attrQuery = createQuery()
$: attrQuery.query(core.class.Attribute, { attributeOf: _class }, () => {
attributes = getCustomAttributes(_class)
})
function update (): void {
attributes = getCustomAttributes(_class)
} }
export function createAttribute (ev: MouseEvent): void { export function createAttribute (ev: MouseEvent): void {
showPopup(TypesPopup, { _class }, getEventPositionElement(ev), (_id) => { showPopup(TypesPopup, { _class }, getEventPositionElement(ev), (_id) => {
if (_id !== undefined) $settingsStore = { component: CreateAttribute, props: { _id, _class } } if (_id !== undefined) $settingsStore = { component: CreateAttribute, props: { _id, _class } }
}) })
// showPopup(CreateAttribute, { _class }, 'top', update)
} }
export async function editAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
showPopup(EditAttribute, { attribute, exist }, 'top', update)
}
export async function removeAttribute (attribute: AnyAttribute, exist: boolean): Promise<void> {
showPopup(
MessageBox,
{
label: settings.string.DeleteAttribute,
message: exist ? settings.string.DeleteAttributeExistConfirm : settings.string.DeleteAttributeConfirm
},
'top',
async (result) => {
if (result != null) {
await client.remove(attribute)
update()
}
}
)
}
async function showMenu (ev: MouseEvent, attribute: AnyAttribute, row: number): Promise<void> {
hovered = row
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exists: true } })) !== undefined
const actions: Action[] = [
{
label: presentation.string.Edit,
icon: IconEdit,
action: async () => {
await selectAttribute(attribute, row)
}
}
]
if (attribute.isCustom === true) {
actions.push({
label: presentation.string.Remove,
icon: IconDelete,
action: async () => {
await removeAttribute(attribute, exist)
}
})
}
const extra = await getContextActions(client, attribute, { mode: 'context' })
actions.push(
...extra.map((it) => ({
label: it.label,
icon: it.icon,
action: async (_: any, evt: Event) => {
const r = await getResource(it.action)
await r(attribute, evt, it.actionProps)
}
}))
)
showPopup(Menu, { actions }, getEventPositionElement(ev), () => {
hovered = null
})
}
function getAttrType (type: Type<any>): IntlString | undefined {
switch (type._class) {
case core.class.RefTo:
return client.getHierarchy().getClass((type as RefTo<Doc>).to).label
case core.class.Collection:
return client.getHierarchy().getClass((type as Collection<AttachedDoc>).of).label
case core.class.ArrOf:
return (type as ArrOf<Doc>).of.label
default:
return undefined
}
}
async function getEnumName (type: Type<any>): Promise<string | undefined> {
const ref = (type as EnumOf).of
const res = await client.findOne(core.class.Enum, { _id: ref })
return res?.name
}
function editLabel (evt: MouseEvent): void { function editLabel (evt: MouseEvent): void {
showPopup(EditClassLabel, { clazz }, getEventPositionElement(evt)) showPopup(EditClassLabel, { clazz }, getEventPositionElement(evt))
} }
async function selectAttribute (attribute: AnyAttribute, n: number): Promise<void> {
if (selected === n) {
selected = null
clearSettingsStore()
return
}
selected = n
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exists: true } })) !== undefined
$settingsStore = { component: EditAttribute, props: { attribute, exist } }
}
settingsStore.subscribe((value) => {
if (value.component == null) selected = null
})
const classUpdated = (_clazz: Ref<Class<Doc>>): void => { const classUpdated = (_clazz: Ref<Class<Doc>>): void => {
selected = null selected = undefined
classes = client
.getHierarchy()
.getAncestors(_class)
.map((it) => client.getHierarchy().getClass(it))
.filter((it) => {
return (
!it.hidden &&
it.label !== undefined &&
it._id !== core.class.Doc &&
it._id !== core.class.AttachedDoc &&
it._id !== _class
)
})
clazzHierarchy = client.getHierarchy().getClass(_class)
} }
$: classUpdated(_class) $: classUpdated(_class)
settingsStore.subscribe((value) => {
if (value.id === undefined) selected = undefined
})
const handleDeselect = (): void => {
selected = undefined
clearSettingsStore()
}
const handleSelect = async (event: CustomEvent): Promise<void> => {
selected = event.detail as AnyAttribute
const exist = (await client.findOne(selected.attributeOf, { [selected.name]: { $exists: true } })) !== undefined
$settingsStore = { id: selected._id, component: EditAttribute, props: { attribute: selected, exist } }
}
onDestroy(() => {
if (selected !== undefined) clearSettingsStore()
})
</script> </script>
{#if showTitle} {#if showTitle}
@ -222,11 +133,17 @@
{/if} {/if}
{/if} {/if}
<div class="hulyTableAttr-container"> <div class="hulyTableAttr-container">
<div class="hulyTableAttr-header font-medium-12"> <div class="hulyTableAttr-header font-medium-12" class:withButton={showHierarchy}>
{#if showHierarchy}
<ModernButton icon={IconSettings} kind={'secondary'} size={'small'} hasMenu>
<Label label={settings.string.ClassColon} />
<ObjectPresenter _class={clazzHierarchy._class} objectId={clazzHierarchy._id} value={clazzHierarchy} />
</ModernButton>
{:else}
<IconSettings size={'small'} /> <IconSettings size={'small'} />
<span><Label label={settings.string.ClassProperties} /></span> <span><Label label={settings.string.ClassProperties} /></span>
{/if}
<ButtonIcon <ButtonIcon
bind:this={btnAdd}
kind={'primary'} kind={'primary'}
icon={IconAdd} icon={IconAdd}
size={'small'} size={'small'}
@ -235,63 +152,50 @@
}} }}
/> />
</div> </div>
{#if attributes.length} {#if showHierarchy}
<div class="hulyTableAttr-content"> <div class="hulyTableAttr-content class withTitle">
{#each attributes as attr, i} <div class="hulyTableAttr-content__title">
{@const attrType = getAttrType(attr.type)} <Label label={settings.string.Properties} />
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="hulyTableAttr-content__row"
class:hovered={hovered === i}
class:selected={selected === i}
on:contextmenu={(ev) => {
ev.preventDefault()
void showMenu(ev, attr, i)
}}
on:click={async () => {
void selectAttribute(attr, i)
}}
>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="hulyTableAttr-content__row-dragMenu" on:click|stopPropagation={(ev) => showMenu(ev, attr, i)}>
<IconMoreV2 size={'small'} />
</div> </div>
{#if attr.isCustom} <div class="hulyTableAttr-content__wrapper">
<div class="hulyChip-item font-medium-12"> <ClassAttributesList
<Label label={settings.string.Custom} /> {_class}
{ofClass}
{attributeMapper}
{selected}
on:deselect={handleDeselect}
on:select={handleSelect}
/>
</div> </div>
{/if}
{#if attr.icon !== undefined}
<div class="hulyTableAttr-content__row-icon">
<Icon icon={attr.icon} size={'small'} />
</div> </div>
{/if} {#each classes as clazz2}
<div class="hulyTableAttr-content__row-label font-regular-14" class:accent={!attr.hidden}> <div class="hulyTableAttr-content class withTitle">
<Label label={attr.label} /> <div class="hulyTableAttr-content__title">
<Label label={clazz2.label} />
</div> </div>
{#if attributeMapper} <div class="hulyTableAttr-content__wrapper">
<svelte:component this={attributeMapper.component} {...attributeMapper.props} attribute={attr} /> <ClassAttributesList
{/if} _class={clazz2._id}
<div class="hulyTableAttr-content__row-type font-medium-12"> {ofClass}
<Label label={attr.type.label} /> {attributeMapper}
{#if attrType !== undefined} {selected}
: <Label label={attrType} /> notUseOfClass
{/if} on:deselect={handleDeselect}
{#if attr.type._class === core.class.EnumOf} on:select={handleSelect}
{#await getEnumName(attr.type) then name} />
{#if name}
: {name}
{/if}
{/await}
{/if}
</div>
<div class="hulyTableAttr-content__row-arrow">
<IconOpenedArrow size={'small'} />
</div> </div>
</div> </div>
{/each} {/each}
{:else if hasCustomAttributes(_class)}
<div class="hulyTableAttr-content class">
<ClassAttributesList
{_class}
{ofClass}
{attributeMapper}
{selected}
on:deselect={handleDeselect}
on:select={handleSelect}
/>
</div> </div>
{/if} {/if}
</div> </div>
@ -306,114 +210,4 @@
line-height: 2rem; line-height: 2rem;
color: var(--input-TextColor); color: var(--input-TextColor);
} }
.hulyTableAttr-container {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
background-color: var(--theme-table-row-color);
border: 1px solid var(--theme-divider-color);
border-radius: var(--large-BorderRadius);
.hulyTableAttr-header {
display: flex;
justify-content: space-between;
align-items: center;
align-self: stretch;
flex-shrink: 0;
padding: var(--spacing-2) var(--spacing-2) var(--spacing-2) var(--spacing-2_5);
text-transform: uppercase;
color: var(--global-secondary-TextColor);
span {
flex-grow: 1;
margin-left: var(--spacing-1_5);
}
}
.hulyTableAttr-content {
display: flex;
flex-direction: column;
align-items: flex-start;
align-self: stretch;
flex-shrink: 0;
padding: var(--spacing-1);
border-top: 1px solid var(--theme-divider-color);
&__row {
display: flex;
align-items: center;
align-self: stretch;
gap: var(--spacing-1);
padding: var(--spacing-1) var(--spacing-2) var(--spacing-1) var(--spacing-1);
border-radius: var(--small-BorderRadius);
cursor: pointer;
&-dragMenu {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
width: var(--global-extra-small-Size);
height: var(--global-extra-small-Size);
border-radius: var(--extra-small-BorderRadius);
}
&-icon {
width: var(--global-min-Size);
height: var(--global-min-Size);
color: var(--global-primary-TextColor);
}
&-label {
white-space: nowrap;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: flex;
align-items: center;
flex-grow: 1;
min-width: 0;
color: var(--global-primary-TextColor);
&.accent {
font-weight: 500;
}
}
&-type {
text-transform: uppercase;
color: var(--global-secondary-TextColor);
}
&-arrow {
display: none;
flex-shrink: 0;
width: var(--global-min-Size);
height: var(--global-min-Size);
color: var(--global-primary-LinkColor);
}
&.hovered,
&:hover {
background-color: var(--theme-table-header-color); // var(--global-surface-03-hover-BackgroundColor);
}
&.selected {
background-color: var(--theme-table-header-color); // var(--global-surface-03-hover-BackgroundColor);
.hulyTableAttr-content__row-icon,
.hulyTableAttr-content__row-arrow,
.hulyTableAttr-content__row-label {
color: var(--global-primary-LinkColor);
}
.hulyTableAttr-content__row-type {
color: var(--global-primary-TextColor);
}
.hulyTableAttr-content__row-label {
font-weight: 700;
}
}
&.hovered .hulyTableAttr-content__row-arrow,
&:hover .hulyTableAttr-content__row-arrow,
&.selected .hulyTableAttr-content__row-arrow {
display: block;
}
}
}
}
</style> </style>

View File

@ -13,50 +13,28 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import core, { import { createEventDispatcher } from 'svelte'
AnyAttribute, import core, { AnyAttribute, ArrOf, AttachedDoc, Class, Collection, Doc, Ref, RefTo, Type } from '@hcengineering/core'
ArrOf,
AttachedDoc,
Class,
ClassifierKind,
Collection,
Doc,
EnumOf,
Ref,
RefTo,
Type
} from '@hcengineering/core'
import { IntlString, getResource } from '@hcengineering/platform' import { IntlString, getResource } from '@hcengineering/platform'
import presentation, { MessageBox, createQuery, getClient } from '@hcengineering/presentation' import presentation, { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
import { import {
Action, Action,
ActionIcon,
AnySvelteComponent, AnySvelteComponent,
CircleButton,
Icon,
IconAdd,
IconDelete, IconDelete,
IconEdit, IconEdit,
IconMoreV2,
Label,
Menu, Menu,
getEventPositionElement, getEventPositionElement,
getEventPopupPositionElement,
showPopup showPopup
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { getContextActions } from '@hcengineering/view-resources' import { getContextActions } from '@hcengineering/view-resources'
import settings from '../plugin' import settings from '../plugin'
import CreateAttribute from './CreateAttribute.svelte' import ClassAttributeRow from './ClassAttributeRow.svelte'
import EditAttribute from './EditAttribute.svelte' import EditAttribute from './EditAttribute.svelte'
import EditClassLabel from './EditClassLabel.svelte'
import { settingsStore } from '../store'
import TypesPopup from './typeEditors/TypesPopup.svelte'
export let _class: Ref<Class<Doc>> export let _class: Ref<Class<Doc>>
export let ofClass: Ref<Class<Doc>> | undefined = undefined export let ofClass: Ref<Class<Doc>> | undefined = undefined
export let useOfClassAttributes = true export let notUseOfClass: boolean = false
export let showTitle = true export let selected: AnyAttribute | undefined = undefined
export let showCreate = true
export let attributeMapper: export let attributeMapper:
| { | {
@ -66,12 +44,15 @@
} }
| undefined = undefined | undefined = undefined
const dispatch = createEventDispatcher()
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() const hierarchy = client.getHierarchy()
const classQuery = createQuery() const classQuery = createQuery()
let clazz: Class<Doc> | undefined let clazz: Class<Doc> | undefined
let hovered: number | null = null
$: classQuery.query(core.class.Class, { _id: _class }, (res) => { $: classQuery.query(core.class.Class, { _id: _class }, (res) => {
clazz = res.shift() clazz = res.shift()
@ -81,9 +62,7 @@
function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] { function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] {
const cl = hierarchy.getClass(_class) const cl = hierarchy.getClass(_class)
const attributes = Array.from( const attributes = Array.from(
hierarchy hierarchy.getAllAttributes(_class, _class === ofClass && !notUseOfClass ? core.class.Doc : cl.extends).values()
.getAllAttributes(_class, _class === ofClass && useOfClassAttributes ? core.class.Doc : cl.extends)
.values()
) )
return attributes return attributes
} }
@ -98,12 +77,6 @@
attributes = getCustomAttributes(_class) attributes = getCustomAttributes(_class)
} }
export function createAttribute (ev: MouseEvent): void {
showPopup(TypesPopup, { _class }, getEventPopupPositionElement(ev), (_id) => {
if (_id !== undefined) $settingsStore = { component: CreateAttribute, props: { _id, _class } }
})
}
export 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) showPopup(EditAttribute, { attribute, exist }, 'top', update)
} }
@ -125,7 +98,8 @@
) )
} }
async function showMenu (ev: MouseEvent, attribute: AnyAttribute): Promise<void> { async function showMenu (ev: MouseEvent, attribute: AnyAttribute, row: number): Promise<void> {
hovered = row
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exists: true } })) !== undefined const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exists: true } })) !== undefined
const actions: Action[] = [ const actions: Action[] = [
@ -133,7 +107,7 @@
label: presentation.string.Edit, label: presentation.string.Edit,
icon: IconEdit, icon: IconEdit,
action: async () => { action: async () => {
await editAttribute(attribute, exist) dispatch('select', attribute._id)
} }
} }
] ]
@ -157,7 +131,9 @@
} }
})) }))
) )
showPopup(Menu, { actions }, getEventPositionElement(ev)) showPopup(Menu, { actions }, getEventPositionElement(ev), () => {
hovered = null
})
} }
function getAttrType (type: Type<any>): IntlString | undefined { function getAttrType (type: Type<any>): IntlString | undefined {
@ -172,116 +148,27 @@
return undefined return undefined
} }
} }
async function getEnumName (type: Type<any>): Promise<string | undefined> {
const ref = (type as EnumOf).of
const res = await client.findOne(core.class.Enum, { _id: ref })
return res?.name
}
function editLabel (evt: MouseEvent): void {
showPopup(EditClassLabel, { clazz }, getEventPositionElement(evt))
}
</script> </script>
{#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 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>
{/if}
{/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} {#each attributes as attr, i}
{@const attrType = getAttrType(attr.type)} {@const attrType = getAttrType(attr.type)}
<tr <ClassAttributeRow
class="antiTable-body__row" attribute={attr}
on:contextmenu={(ev) => { attributeType={attrType}
ev.preventDefault() selected={selected && attr._id === selected._id}
void showMenu(ev, attr) hovered={hovered === i}
{attributeMapper}
clickMore={async (event) => {
event.preventDefault()
void showMenu(event, attr, i)
}} }}
> on:contextmenu={async (event) => {
<td> event.preventDefault()
{#if i === 0 && clazz?.label !== undefined} void showMenu(event, attr, i)
<div class="trans-title"> }}
<Label label={clazz.label} /> on:click={async () => {
</div> if (selected && selected._id === attr._id) dispatch('deselect')
{/if} else dispatch('select', attr)
</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>
{/if}
{#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} {/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}

View File

@ -110,7 +110,7 @@
{/if} {/if}
<div class="hulyComponent-content__container columns"> <div class="hulyComponent-content__container columns">
<div class="hulyComponent-content__column"> <div class="hulyComponent-content__column">
<div class="hulyComponent-content__navHeader"> <div class="hulyComponent-content__navHeader divide">
<div class="hulyComponent-content__navHeader-menu"> <div class="hulyComponent-content__navHeader-menu">
<ButtonIcon kind={'tertiary'} icon={IconDescription} size={'small'} inheritColor /> <ButtonIcon kind={'tertiary'} icon={IconDescription} size={'small'} inheritColor />
</div> </div>
@ -134,13 +134,13 @@
</div> </div>
<Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} /> <Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} />
<div class="hulyComponent-content__column content"> <div class="hulyComponent-content__column content">
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content"> <div class="hulyComponent-content">
{#if _class !== undefined} {#if _class !== undefined}
<Scroller> <ClassAttributes {_class} {ofClass} {attributeMapper} />
<ClassAttributes {_class} {ofClass} {attributeMapper} {useOfClassAttributes} />
</Scroller>
{/if} {/if}
</div> </div>
</Scroller>
</div> </div>
</div> </div>
</div> </div>

View File

@ -16,7 +16,7 @@
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { PluginConfiguration } from '@hcengineering/core' import { PluginConfiguration } from '@hcengineering/core'
import { configurationStore, getClient } from '@hcengineering/presentation' import { configurationStore, getClient } from '@hcengineering/presentation'
import { Button, Icon, IconInfo, Label, Header, Breadcrumb } from '@hcengineering/ui' import { Button, Icon, IconInfo, Label, Header, Breadcrumb, Scroller } from '@hcengineering/ui'
import setting from '../plugin' import setting from '../plugin'
export let visibleNav: boolean = true export let visibleNav: boolean = true
@ -37,6 +37,7 @@
<Breadcrumb icon={setting.icon.Setting} label={setting.string.Configuration} size={'large'} isCurrent /> <Breadcrumb icon={setting.icon.Setting} label={setting.string.Configuration} size={'large'} isCurrent />
</Header> </Header>
<div class="hulyComponent-content__column content"> <div class="hulyComponent-content__column content">
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="flex-row-center flex-wrap gap-around-4"> <div class="flex-row-center flex-wrap gap-around-4">
{#each $configurationStore.list as config} {#each $configurationStore.list as config}
{#if config.label} {#if config.label}
@ -70,6 +71,7 @@
{/if} {/if}
{/each} {/each}
</div> </div>
</Scroller>
</div> </div>
</div> </div>

View File

@ -28,7 +28,8 @@
Breadcrumb, Breadcrumb,
defineSeparators, defineSeparators,
settingsSeparators, settingsSeparators,
Separator Separator,
Scroller
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources' import { ContextMenu } from '@hcengineering/view-resources'
import setting from '../plugin' import setting from '../plugin'
@ -104,11 +105,13 @@
</div> </div>
<Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} /> <Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} />
<div class="hulyComponent-content__column content"> <div class="hulyComponent-content__column content">
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content"> <div class="hulyComponent-content">
{#if selected !== undefined} {#if selected !== undefined}
<EnumValues value={selected} /> <EnumValues value={selected} />
{/if} {/if}
</div> </div>
</Scroller>
</div> </div>
</div> </div>
</div> </div>

View File

@ -18,7 +18,7 @@
import { EmployeePresenter, personByIdStore } from '@hcengineering/contact-resources' import { EmployeePresenter, personByIdStore } from '@hcengineering/contact-resources'
import { AccountRole, SortingOrder, getCurrentAccount } from '@hcengineering/core' import { AccountRole, SortingOrder, getCurrentAccount } from '@hcengineering/core'
import presentation, { createQuery, getClient } from '@hcengineering/presentation' import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Header, Breadcrumb } from '@hcengineering/ui' import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Header, Breadcrumb, Scroller } from '@hcengineering/ui'
import setting from '../plugin' import setting from '../plugin'
export let visibleNav: boolean = true export let visibleNav: boolean = true
@ -65,6 +65,7 @@
<EditBox kind={'search-style'} focusIndex={1} bind:value={search} placeholder={presentation.string.Search} /> <EditBox kind={'search-style'} focusIndex={1} bind:value={search} placeholder={presentation.string.Search} />
</Header> </Header>
<div class="hulyComponent-content__column content"> <div class="hulyComponent-content__column content">
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content"> <div class="hulyComponent-content">
{#each accounts as account (account._id)} {#each accounts as account (account._id)}
{@const employee = $personByIdStore.get(account.person)} {@const employee = $personByIdStore.get(account.person)}
@ -92,5 +93,6 @@
{/if} {/if}
{/each} {/each}
</div> </div>
</Scroller>
</div> </div>
</div> </div>

View File

@ -78,6 +78,7 @@ export default mergeIds(settingId, setting, {
ConfigEnable: '' as IntlString, ConfigEnable: '' as IntlString,
ConfigBeta: '' as IntlString, ConfigBeta: '' as IntlString,
ClassSettingHint: '' as IntlString, ClassSettingHint: '' as IntlString,
ClassProperties: '' as IntlString ClassProperties: '' as IntlString,
ClassColon: '' as IntlString
} }
}) })

View File

@ -20,8 +20,9 @@ import { writable } from 'svelte/store'
* @public * @public
*/ */
export interface SettingsStore { export interface SettingsStore {
component?: ComponentType | null id?: any | undefined
props?: object | null component?: ComponentType | undefined
props?: object | undefined
} }
/** /**
@ -33,5 +34,5 @@ export const settingsStore = writable<SettingsStore>({})
* @public * @public
*/ */
export const clearSettingsStore = (): void => { export const clearSettingsStore = (): void => {
settingsStore.set({ component: null, props: null }) settingsStore.set({ id: undefined, component: undefined, props: undefined })
} }

View File

@ -169,7 +169,11 @@ export default plugin(settingId, {
Classes: '' as IntlString, Classes: '' as IntlString,
Owners: '' as IntlString, Owners: '' as IntlString,
Configure: '' as IntlString, Configure: '' as IntlString,
InviteSettings: '' as IntlString InviteSettings: '' as IntlString,
Properties: '' as IntlString,
TaskTypes: '' as IntlString,
Automations: '' as IntlString,
Collections: '' as IntlString
}, },
icon: { icon: {
AccountSettings: '' as Asset, AccountSettings: '' as Asset,

View File

@ -84,6 +84,10 @@
"TaskType": "Task type", "TaskType": "Task type",
"ManageProjects": "Project types", "ManageProjects": "Project types",
"CreateProjectType": "Create project type", "CreateProjectType": "Create project type",
"ClassicProject": "Classic project" "ClassicProject": "Classic project",
"LastSave": "Last save",
"Published": "Published",
"CountProjects": "{count, plural, =0 {No projects} =1 {# project} other {# projects}}",
"ProjectTypeTitle": "Project type title"
} }
} }

View File

@ -84,6 +84,10 @@
"TaskType": "Тип задачи", "TaskType": "Тип задачи",
"ManageProjects": "Управление проектами", "ManageProjects": "Управление проектами",
"CreateProjectType": "Создать тип проекта", "CreateProjectType": "Создать тип проекта",
"ClassicProject": "Классический проект" "ClassicProject": "Классический проект",
"LastSave": "Последнее сохранение",
"Published": "Опубликовано",
"CountProjects": "{count, plural, =0 {Нет проектов} =1 {# проект} other {# проектов}}",
"ProjectTypeTitle": "Название типа проекта"
} }
} }

View File

@ -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">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path
d="M16 24.0001C15.8345 24.0001 15.6715 23.9589 15.5259 23.8801L3.40621 17.3541C2.92006 17.0923 2.73823 16.486 3.00011 15.9999C3.26191 15.514 3.86801 15.3322 4.35401 15.5939L16 21.8644L27.6456 15.5941C28.1318 15.3323 28.7381 15.5142 29 16.0004C29.2618 16.4866 29.0799 17.0931 28.5937 17.3549L16.4741 23.8808C16.3284 23.9593 16.1655 24.0003 16 24.0001Z"
/>
<path
d="M16 30.0001C15.8345 30.0001 15.6715 29.9589 15.5259 29.8801L3.40621 23.3541C2.92006 23.0923 2.73823 22.486 3.00011 21.9999C3.26191 21.514 3.86801 21.3322 4.35401 21.5939L16 27.8644L27.6456 21.5941C28.1318 21.3323 28.7381 21.5142 29 22.0004C29.2618 22.4866 29.0799 23.0931 28.5937 23.3549L16.4741 29.8808C16.3284 29.9593 16.1655 30.0003 16 30.0001Z"
/>
<path
d="M16 18.0001C15.8345 18.0001 15.6715 17.9589 15.5259 17.8801L2.5259 10.8801C2.36698 10.7945 2.23419 10.6674 2.14164 10.5124C2.04909 10.3574 2.00023 10.1803 2.00023 9.99976C2.00023 9.81923 2.04909 9.64208 2.14164 9.48708C2.23419 9.33209 2.36698 9.20503 2.5259 9.11941L15.5259 2.11941C15.6716 2.04079 15.8345 1.99963 16 1.99963C16.1655 1.99963 16.3284 2.04079 16.4741 2.11941L29.4741 9.11941C29.633 9.20503 29.7658 9.33209 29.8584 9.48708C29.9509 9.64208 29.9998 9.81923 29.9998 9.99976C29.9998 10.1803 29.9509 10.3574 29.8584 10.5124C29.7658 10.6674 29.633 10.7945 29.4741 10.8801L16.4741 17.8801C16.3285 17.9589 16.1655 18.0001 16 18.0001ZM5.1094 10.0001L16 15.8644L26.8906 10.0001L16 4.13591L5.1094 10.0001Z"
/>
</svg>

View File

@ -15,10 +15,10 @@
--> -->
<script lang="ts"> <script lang="ts">
import { Ref } from '@hcengineering/core' import { Ref } from '@hcengineering/core'
import { createQuery, hasResource } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
import setting from '@hcengineering/setting' import setting from '@hcengineering/setting'
import task, { ProjectTypeDescriptor } from '@hcengineering/task' import task, { ProjectTypeDescriptor } from '@hcengineering/task'
import { Component, Icon, IconChevronDown, Label } from '@hcengineering/ui' import { Icon, IconChevronDown, Label } from '@hcengineering/ui'
export let descriptor: ProjectTypeDescriptor | undefined export let descriptor: ProjectTypeDescriptor | undefined
export let descriptorId: Ref<ProjectTypeDescriptor> | undefined export let descriptorId: Ref<ProjectTypeDescriptor> | undefined
@ -32,7 +32,7 @@
}) })
$: if (descriptor === undefined && categories.length > 0) { $: if (descriptor === undefined && categories.length > 0) {
descriptor = categories.filter((f) => hasResource(f.icon))[0] descriptor = categories.filter((f) => f.icon)[0]
} }
function select (item: ProjectTypeDescriptor): void { function select (item: ProjectTypeDescriptor): void {
@ -45,7 +45,7 @@
</div> </div>
<div class="flex-col overflow-y-auto"> <div class="flex-col overflow-y-auto">
{#each categories as f (f._id)} {#each categories as f (f._id)}
{#if hasResource(f.icon)} {#if f.icon}
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<div <div
@ -56,7 +56,7 @@
}} }}
> >
<div class="icon flex-no-shrink mr-4"> <div class="icon flex-no-shrink mr-4">
<Component is={f.icon} /> <Icon icon={f.icon} size={'small'} />
</div> </div>
<div class="flex-grow flex-col"> <div class="flex-grow flex-col">
<div class="fs-title overflow-label"> <div class="fs-title overflow-label">

View File

@ -18,19 +18,20 @@
import { ComponentExtensions, createQuery, getClient } from '@hcengineering/presentation' import { ComponentExtensions, createQuery, getClient } from '@hcengineering/presentation'
import task, { Project, ProjectType, ProjectTypeDescriptor, Task, TaskType } from '@hcengineering/task' import task, { Project, ProjectType, ProjectTypeDescriptor, Task, TaskType } from '@hcengineering/task'
import { Ref, SortingOrder, Status } from '@hcengineering/core' import { Ref, SortingOrder } from '@hcengineering/core'
import { getEmbeddedLabel } from '@hcengineering/platform' import { getEmbeddedLabel } from '@hcengineering/platform'
import { import type { Asset, IntlString } from '@hcengineering/platform'
Button, import ui, {
ButtonIcon, ButtonIcon,
Component, Component,
EditBox, ModernEditbox,
Icon, TextArea,
IconAdd, IconAdd,
IconCopy, IconCopy,
IconDelete, IconDelete,
IconFile, IconSquareExpand,
IconMoreV, IconMoreV,
IconFolder,
Label, Label,
Location, Location,
eventToHTMLElement, eventToHTMLElement,
@ -39,15 +40,25 @@
resolvedLocationStore, resolvedLocationStore,
showPopup, showPopup,
Header, Header,
Breadcrumbs Breadcrumbs,
ModernButton,
IconSend,
IconDescription,
Separator,
Scroller,
defineSeparators,
secondNavSeparators,
NavItem
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources' import { ContextMenu } from '@hcengineering/view-resources'
import plugin from '../../plugin' import plugin from '../../plugin'
import setting from '@hcengineering/setting'
import { ClassAttributes } from '@hcengineering/setting-resources'
import CreateTaskType from '../taskTypes/CreateTaskType.svelte' import CreateTaskType from '../taskTypes/CreateTaskType.svelte'
import TaskTypeEditor from '../taskTypes/TaskTypeEditor.svelte' import TaskTypeEditor from '../taskTypes/TaskTypeEditor.svelte'
import TaskTypeIcon from '../taskTypes/TaskTypeIcon.svelte' import TaskTypeIcon from '../taskTypes/TaskTypeIcon.svelte'
import TaskTypeKindEditor from '../taskTypes/TaskTypeKindEditor.svelte' import TaskTypeKindEditor from '../taskTypes/TaskTypeKindEditor.svelte'
import TypeClassEditor from '../taskTypes/TypeClassEditor.svelte' import IconLayers from '../icons/Layers.svelte'
export let type: ProjectType export let type: ProjectType
export let descriptor: ProjectTypeDescriptor | undefined export let descriptor: ProjectTypeDescriptor | undefined
@ -143,8 +154,23 @@
} }
$: items = $: items =
selectedTaskType !== undefined selectedTaskType !== undefined
? [{ label: plugin.string.ProjectType }, { title: selectedTaskType.name }] ? [
: [{ label: plugin.string.ProjectType }] { label: plugin.string.ProjectType, icon: descriptor?.icon },
{ title: selectedTaskType.name, icon: selectedTaskType.icon }
]
: [{ label: plugin.string.ProjectType, icon: descriptor?.icon }]
const navigator: {
id: string
label: IntlString
}[] = [
{ 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> </script>
{#if type !== undefined && descriptor !== undefined} {#if type !== undefined && descriptor !== undefined}
@ -180,26 +206,56 @@
on:select={(event) => { on:select={(event) => {
if (event.detail === 0) selectTaskType(undefined) 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> </Header>
<div class="hulyComponent-content__container columns">
{#if selectedTaskTypeId === undefined}
<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 (navItem.id)}
<NavItem type={'type-anchor-link'} label={navItem.label} />
{/each}
</div>
<Separator name={'typeSettings'} index={0} color={'transparent'} />
{/if}
<div class="hulyComponent-content__column content"> <div class="hulyComponent-content__column content">
<div class="hulyComponent-content"> <Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content gap">
{#if selectedTaskType === undefined} {#if selectedTaskType === undefined}
<!-- <div class="flex-col">Navigation</div> --> <div class="hulyComponent-content__column-group">
<div class="flex-grow h-full"> <div class="hulyComponent-content__header">
<div class="p-4 flex-col"> <ButtonIcon icon={descriptor.icon} size={'large'} kind={'secondary'} />
<div>
<div class="flex-row-center flex-between mb-2">
<div>
<Component is={descriptor.icon} props={{ size: 'large' }} />
</div>
<div class="flex-row-center no-word-wrap"> <div class="flex-row-center no-word-wrap">
<Icon icon={IconFile} size={'small'} /> <ModernButton
{projects.length} projects icon={IconSquareExpand}
label={plugin.string.CountProjects}
labelParams={{ count: projects.length }}
disabled={projects.length === 0}
kind={'tertiary'}
size={'medium'}
hasMenu
/>
</div> </div>
</div> </div>
<EditBox <ModernEditbox
kind={'large-style'} kind={'ghost'}
size={'large'}
label={plugin.string.ProjectTypeTitle}
value={type?.name ?? ''} value={type?.name ?? ''}
on:blur={(evt) => { on:blur={(evt) => {
if (type !== undefined) { if (type !== undefined) {
@ -207,111 +263,75 @@
} }
}} }}
/> />
<div class="p-2"> <TextArea
<EditBox
placeholder={getEmbeddedLabel('Description')} placeholder={getEmbeddedLabel('Description')}
kind={'small-style'} width={'100%'}
height={'4.5rem'}
margin={'var(--spacing-1) var(--spacing-2)'}
noFocusBorder
bind:value={type.shortDescription} bind:value={type.shortDescription}
on:change={() => onShortDescriptionChange(type?.shortDescription ?? '')} on:change={() => onShortDescriptionChange(type?.shortDescription ?? '')}
/> />
</div>
{#if descriptor?.editor} {#if descriptor?.editor}
<Component is={descriptor.editor} props={{ type }} /> <Component is={descriptor.editor} props={{ type }} />
{/if} {/if}
</div> </div>
<div class="panelBox flex-col row"> <ClassAttributes ofClass={descriptor.baseClass} _class={type.targetClass} showHierarchy />
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions --> <div class="hulyTableAttr-container">
<div class="fs-title flex flex-between bottom-divider"> <div class="hulyTableAttr-header font-medium-12">
<div class="trans-title"> <IconLayers size={'small'} />
<Label label={getEmbeddedLabel('Task types')} /> <span><Label label={setting.string.TaskTypes} /></span>
</div> <ButtonIcon
<div class="p-1">
<Button
icon={IconAdd}
kind={'primary'} kind={'primary'}
icon={IconAdd}
size={'small'} size={'small'}
on:click={(event) => { on:click={(ev) => {
showPopup(CreateTaskType, { type, descriptor }, 'top') showPopup(CreateTaskType, { type, descriptor }, 'top')
}} }}
/> />
</div> </div>
</div> {#if taskTypes.length}
<div class="mt-1"> <div class="hulyTableAttr-content task">
<!-- svelte-ignore a11y-no-static-element-interactions -->
{#each taskTypes as taskType} {#each taskTypes as taskType}
<!-- svelte-ignore a11y-click-events-have-key-events --> <button
<div class="hulyTableAttr-content__row"
class="flex-grow p-2 antiButton sh-round flex-row-center"
class:regular={taskType._id === selectedTaskTypeId}
class:ghost={taskType._id !== selectedTaskTypeId}
on:click|stopPropagation={() => { on:click|stopPropagation={() => {
selectTaskType(taskType._id) selectTaskType(taskType._id)
}} }}
> >
<div class="p-2"> <div class="hulyTableAttr-content__row-icon-wrapper">
<TaskTypeIcon value={taskType} size={'small'} /> <TaskTypeIcon value={taskType} size={'small'} />
</div> </div>
<div class="fs-title"> {#if taskType.name}
<div class="hulyTableAttr-content__row-label font-medium-14">
{taskType.name} {taskType.name}
</div> </div>
<div class="ml-2 text-sm"> {/if}
<div class="hulyTableAttr-content__row-label grow dark font-regular-14">
<TaskTypeKindEditor readonly kind={taskType.kind} buttonKind={'link'} /> <TaskTypeKindEditor readonly kind={taskType.kind} buttonKind={'link'} />
</div> </div>
</div> </button>
{/each} {/each}
</div> </div>
{/if}
</div> </div>
<ComponentExtensions extension={task.extensions.ProjectEditorExtension} props={{ type }} /> <ComponentExtensions extension={task.extensions.ProjectEditorExtension} props={{ type }} />
<div class="panelBox flex-col row"> <div class="hulyTableAttr-container">
<!-- svelte-ignore a11y-click-events-have-key-events --> <div class="hulyTableAttr-header font-medium-12">
<!-- svelte-ignore a11y-no-static-element-interactions --> <IconFolder size={'small'} />
<div class="fs-title flex flex-between bottom-divider"> <span><Label label={setting.string.Collections} /></span>
<div class="trans-title"> <ButtonIcon kind={'primary'} icon={IconAdd} size={'small'} on:click={() => {}} />
<Label label={getEmbeddedLabel('Collections')} />
</div>
<div class="p-1">
<Button icon={IconAdd} kind={'primary'} size={'small'} on:click={(event) => {}} />
</div>
</div>
<div class="mt-1">
<!-- svelte-ignore a11y-no-static-element-interactions -->
</div>
</div>
<div class="panelBox flex-col row">
<div class="mt-1">
<TypeClassEditor ofClass={descriptor.baseClass} _class={type.targetClass} />
</div>
</div>
</div> </div>
</div> </div>
{:else} {:else}
<TaskTypeEditor taskType={selectedTaskType} projectType={type} {taskTypes} {taskTypeCounter} /> <TaskTypeEditor taskType={selectedTaskType} projectType={type} {taskTypes} {taskTypeCounter} />
{/if} {/if}
</div> </div>
</Scroller>
</div>
</div> </div>
{/if} {/if}
<style lang="scss">
.row {
// border-bottom: 1px solid var(--theme-list-border-color);
// border-top: 1px solid var(--theme-list-border-color);
width: 100%;
}
.panelBox {
margin-top: 1rem;
padding: 0.5rem;
// TODO: Need to update it
border: 1px solid var(--theme-button-border);
background: var(--theme-button-default);
border-radius: 8px;
}
.editorBox {
max-width: 1024px;
min-width: 768px;
}
</style>

View File

@ -16,7 +16,7 @@
import { Ref } from '@hcengineering/core' import { Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation' import { getClient } from '@hcengineering/presentation'
import task, { type ProjectType } from '@hcengineering/task' import task, { type ProjectType } from '@hcengineering/task'
import { Component } from '@hcengineering/ui' import { Icon } from '@hcengineering/ui'
import { typeStore } from '../..' import { typeStore } from '../..'
export let value: ProjectType | Ref<ProjectType> | undefined export let value: ProjectType | Ref<ProjectType> | undefined
@ -29,7 +29,7 @@
{#if _value !== undefined} {#if _value !== undefined}
<span class="label flex-row-center gap-1 ml-3 no-word-wrap"> <span class="label flex-row-center gap-1 ml-3 no-word-wrap">
{#if descriptor?.icon} {#if descriptor?.icon}
<Component is={descriptor?.icon} props={{ size: 'small' }} /> <Icon icon={descriptor?.icon} size={'small'} />
{/if} {/if}
<span class:ml-1={descriptor?.icon !== undefined}> <span class:ml-1={descriptor?.icon !== undefined}>
{_value.name} {_value.name}

View File

@ -16,7 +16,7 @@
<script lang="ts"> <script lang="ts">
import { Ref, WithLookup } from '@hcengineering/core' import { Ref, WithLookup } from '@hcengineering/core'
import { ProjectType } from '@hcengineering/task' import { ProjectType } from '@hcengineering/task'
import { Component, Label, IconOpenedArrow } from '@hcengineering/ui' import { Icon, Label, IconOpenedArrow } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
export let type: ProjectType | undefined export let type: ProjectType | undefined
@ -42,7 +42,7 @@
<div class="hulyTaskNavLink-avatar"> <div class="hulyTaskNavLink-avatar">
{#if typeItem.$lookup?.descriptor?.icon} {#if typeItem.$lookup?.descriptor?.icon}
<div class="hulyTaskNavLink-icon"> <div class="hulyTaskNavLink-icon">
<Component is={typeItem.$lookup?.descriptor?.icon} props={{ size: 'small', fill: 'currentColor' }} /> <Icon icon={typeItem.$lookup?.descriptor?.icon} size={'small'} fill={'currentColor'} />
</div> </div>
{/if} {/if}
</div> </div>

View File

@ -16,23 +16,20 @@
<script lang="ts"> <script lang="ts">
import { AttributeEditor, getClient } from '@hcengineering/presentation' import { AttributeEditor, getClient } from '@hcengineering/presentation'
import task, { ProjectType, TaskType, calculateStatuses } from '@hcengineering/task' import task, { ProjectType, TaskType, calculateStatuses } from '@hcengineering/task'
import { Ref, Status } from '@hcengineering/core' import { Ref, Status } from '@hcengineering/core'
import { Asset, getEmbeddedLabel } from '@hcengineering/platform' import { Asset, getEmbeddedLabel } from '@hcengineering/platform'
import { Label, showPopup } from '@hcengineering/ui' import { Label, showPopup } from '@hcengineering/ui'
import { IconPicker, statusStore } from '@hcengineering/view-resources' import { IconPicker, statusStore } from '@hcengineering/view-resources'
import { ClassAttributes } from '@hcengineering/setting-resources'
import { taskTypeStore } from '../..' import { taskTypeStore } from '../..'
import StatesProjectEditor from '../state/StatesProjectEditor.svelte' import StatesProjectEditor from '../state/StatesProjectEditor.svelte'
import TaskTypeKindEditor from '../taskTypes/TaskTypeKindEditor.svelte' import TaskTypeKindEditor from '../taskTypes/TaskTypeKindEditor.svelte'
import TaskTypeRefEditor from '../taskTypes/TaskTypeRefEditor.svelte' import TaskTypeRefEditor from '../taskTypes/TaskTypeRefEditor.svelte'
import TaskTypeIcon from './TaskTypeIcon.svelte' import TaskTypeIcon from './TaskTypeIcon.svelte'
import TypeClassEditor from './TypeClassEditor.svelte'
export let projectType: ProjectType export let projectType: ProjectType
export let taskType: TaskType export let taskType: TaskType
export let taskTypeCounter: Map<Ref<TaskType>, number> export let taskTypeCounter: Map<Ref<TaskType>, number>
export let taskTypes: TaskType[] export let taskTypes: TaskType[]
const client = getClient() const client = getClient()
@ -56,8 +53,7 @@
} }
</script> </script>
<div class="p-4 flex-col"> <div class="flex-col">
<div class="flex-col">
<div class="flex-row-center flex-between"> <div class="flex-row-center flex-between">
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
@ -102,8 +98,8 @@
/> />
{/if} {/if}
</div> </div>
</div> </div>
<div class="panelBox"> <div class="panelBox">
<div class="p-2 trans-title"> <div class="p-2 trans-title">
<Label label={getEmbeddedLabel('Process')} /> <Label label={getEmbeddedLabel('Process')} />
</div> </div>
@ -143,15 +139,10 @@
}} }}
/> />
</div> </div>
</div>
<div class="panelBox flex flex-col">
<div class="ml-2">
<TypeClassEditor ofClass={taskType.ofClass} _class={taskType.targetClass} />
</div>
</div>
</div> </div>
<ClassAttributes ofClass={taskType.ofClass} _class={taskType.targetClass} showHierarchy />
<style lang="scss"> <style lang="scss">
.row { .row {
// border-bottom: 1px solid var(--theme-list-border-color); // border-bottom: 1px solid var(--theme-list-border-color);

View File

@ -1,82 +0,0 @@
<!--
// 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 core, { Class, Doc, Obj, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { ClassAttributesList } from '@hcengineering/setting-resources'
import { Button, Icon, IconAdd } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources'
export let ofClass: Ref<Class<Obj>>
export let _class: Ref<Class<Obj>>
const client = getClient()
let classes: Class<Doc>[] = []
$: classes = client
.getHierarchy()
.getAncestors(_class)
.map((it) => client.getHierarchy().getClass(it))
.filter((it) => {
return (
!it.hidden &&
it.label !== undefined &&
it._id !== core.class.Doc &&
it._id !== core.class.AttachedDoc &&
it._id !== _class
)
})
$: clazz = client.getHierarchy().getClass(_class)
let mainAttributes: ClassAttributesList
</script>
<div class="flex flex-between mb-4">
<div class="antiButton regular accent medium sh-round p-2 flex-row-center">
{#if clazz?.icon}
<div class="mr-2 flex">
<Icon icon={clazz.icon} size={'medium'} />
</div>
{/if}
{#if clazz}
<ObjectPresenter _class={clazz._class} objectId={clazz._id} value={clazz} />
{/if}
</div>
<Button icon={IconAdd} size={'small'} kind={'primary'} on:click={(ev) => mainAttributes?.createAttribute(ev)} />
</div>
<div class="ml-2 mr-2">
<table class="antiTable mx-2">
<tbody>
<ClassAttributesList
bind:this={mainAttributes}
{_class}
{ofClass}
useOfClassAttributes={false}
showTitle={false}
showCreate={false}
/>
{#each classes as clazz2}
<ClassAttributesList
_class={clazz2._id}
{ofClass}
useOfClassAttributes={false}
showTitle={false}
showCreate={false}
/>
{/each}
</tbody>
</table>
</div>

View File

@ -77,7 +77,11 @@ export default mergeIds(taskId, task, {
StatusChange: '' as IntlString, StatusChange: '' as IntlString,
TaskCreated: '' as IntlString, TaskCreated: '' as IntlString,
CreateProjectType: '' as IntlString, CreateProjectType: '' as IntlString,
ClassicProject: '' as IntlString ClassicProject: '' as IntlString,
LastSave: '' as IntlString,
Published: '' as IntlString,
CountProjects: '' as IntlString,
ProjectTypeTitle: '' as IntlString
}, },
status: { status: {
AssigneeRequired: '' as IntlString AssigneeRequired: '' as IntlString

View File

@ -185,7 +185,7 @@ export interface ProjectType extends Space {
export interface ProjectTypeDescriptor extends Doc { export interface ProjectTypeDescriptor extends Doc {
name: IntlString name: IntlString
description: IntlString description: IntlString
icon: AnyComponent icon: Asset
editor?: AnyComponent editor?: AnyComponent
baseClass: Ref<Class<Task>> baseClass: Ref<Class<Task>>
} }

View File

@ -18,7 +18,8 @@
Breadcrumb, Breadcrumb,
Separator, Separator,
defineSeparators, defineSeparators,
settingsSeparators settingsSeparators,
Scroller
} from '@hcengineering/ui' } from '@hcengineering/ui'
import { getActions as getContributedActions, TreeItem, TreeNode } from '@hcengineering/view-resources' import { getActions as getContributedActions, TreeItem, TreeNode } from '@hcengineering/view-resources'
import templatesPlugin from '../plugin' import templatesPlugin from '../plugin'
@ -195,6 +196,7 @@
</div> </div>
<Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} /> <Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} />
<div class="hulyComponent-content__column content"> <div class="hulyComponent-content__column content">
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content"> <div class="hulyComponent-content">
{#if newTemplate} {#if newTemplate}
<div class="flex-between mr-4"> <div class="flex-between mr-4">
@ -268,6 +270,7 @@
{/if} {/if}
{/if} {/if}
</div> </div>
</Scroller>
</div> </div>
</div> </div>
</div> </div>