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,
description: board.string.ManageBoardStatuses,
icon: board.component.TemplatesIcon,
icon: board.icon.Board,
baseClass: board.class.Board
},
board.descriptors.BoardType

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,8 @@
.hulyComponent-content,
.hulyComponent-content__container,
.hulyComponent-content__column,
.hulyComponent-content__column.content,
.hulyComponent-content__column-group,
.hulyComponent-content__header,
.hulyComponent-content__navHeader {
display: flex;
width: 100%;
@ -39,6 +40,9 @@
flex-shrink: 0;
max-width: 64rem;
&.gap {
gap: var(--spacing-4);
}
&__container {
height: 100%;
}
@ -54,18 +58,21 @@
margin: 0 0.75rem;
}
&.content {
overflow-y: auto;
align-items: stretch;
}
&-group {
flex-direction: column;
align-items: center;
padding: var(--spacing-3);
flex-shrink: 0;
height: fit-content;
}
}
&__navHeader {
flex-direction: column;
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 {
display: flex;
justify-content: center;
@ -76,9 +83,18 @@
height: var(--global-extra-large-Size);
}
&-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 */
@ -123,8 +139,17 @@
gap: var(--spacing-0_5);
}
.hulyHeader-buttonsGroup {
flex-shrink: 0;
gap: var(--spacing-1);
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 {
margin-right: .625rem;

View File

@ -26,6 +26,7 @@
@import "./mixins.scss";
@import "./panel.scss";
@import "./prose.scss";
@import "./tables.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');

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",
"Cancel": "Cancel",
"Save": "Save",
"Publish": "Publish",
"SaveDraft": "Save draft",
"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}}",
"Days": "{days, plural, =0 {today} =1 {yesterday} other {# days ago}}",

View File

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

View File

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

View File

@ -22,6 +22,7 @@
export let title: string | undefined = undefined
export let label: IntlString | undefined = undefined
export let labelParams: Record<string, any> = {}
export let icon: Asset | AnySvelteComponent | ComponentType | undefined = undefined
export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative'
export let size: 'large' | 'medium' | 'small'
@ -43,10 +44,13 @@
on:click
>
{#if loading}
<div class="icon animate"><Spinner size={'small'} /></div>
{:else if icon}<div class="icon"><Icon {icon} size={'small'} /></div>{/if}
<div class="icon animate"><Spinner size={type === 'type-button' && !hasMenu ? 'medium' : 'small'} /></div>
{: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 label}<span><Label {label} /></span>{/if}
<slot />
</button>
<style lang="scss">
@ -113,7 +117,8 @@
width: var(--spacing-4);
}
}
&.type-button-icon .icon {
&.type-button-icon .icon,
&.menu .icon {
width: var(--spacing-2);
height: var(--spacing-2);
}
@ -123,7 +128,7 @@
background-color: var(--button-primary-BackgroundColor);
.icon {
fill: var(--button-accent-IconColor);
color: var(--button-accent-IconColor);
}
span {
color: var(--button-accent-LabelColor);
@ -145,7 +150,7 @@
cursor: not-allowed;
.icon {
fill: var(--button-disabled-IconColor);
color: var(--button-disabled-IconColor);
}
span {
color: var(--button-disabled-LabelColor);
@ -165,7 +170,7 @@
background-color: var(--button-secondary-BackgroundColor);
.icon {
fill: var(--button-subtle-IconColor);
color: var(--button-subtle-IconColor);
}
span {
color: var(--button-subtle-LabelColor);
@ -187,7 +192,7 @@
cursor: not-allowed;
.icon {
fill: var(--button-disabled-IconColor);
color: var(--button-disabled-IconColor);
}
span {
color: var(--button-disabled-LabelColor);
@ -207,13 +212,13 @@
background-color: transparent;
&:not(.inheritColor) .icon {
fill: var(--button-subtle-IconColor);
color: var(--button-subtle-IconColor);
}
&.inheritColor {
color: inherit;
.icon {
fill: currentColor;
color: currentColor;
}
}
span {
@ -235,7 +240,7 @@
cursor: not-allowed;
.icon {
fill: var(--button-disabled-IconColor);
color: var(--button-disabled-IconColor);
}
span {
color: var(--button-disabled-LabelColor);
@ -255,7 +260,7 @@
background-color: var(--button-negative-BackgroundColor);
.icon {
fill: var(--button-accent-IconColor);
color: var(--button-accent-IconColor);
}
span {
color: var(--button-accent-LabelColor);
@ -277,7 +282,7 @@
cursor: not-allowed;
.icon {
fill: var(--button-disabled-IconColor);
color: var(--button-disabled-IconColor);
}
span {
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">
//
// © 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 Label from './Label.svelte'
import { themeStore } from '..'
@ -26,6 +18,8 @@
export let password: boolean = false
export let limit: number = 0
const dispatch = createEventDispatcher()
$: maxlength = limit === 0 ? null : limit
let placeholderStr: string = ''
@ -48,10 +42,12 @@
spellcheck="false"
{disabled}
{maxlength}
on:blur
on:change
on:keyup
on:input
on:blur={() => {
dispatch('blur', value)
}}
/>
{:else}
<input
@ -64,10 +60,12 @@
spellcheck="false"
{disabled}
{maxlength}
on:blur
on:change
on:keyup
on:input
on:blur={() => {
dispatch('blur', value)
}}
/>
{/if}
{#if labeled}<div class="font-regular-14 label"><Label {label} /></div>{/if}

View File

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

View File

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

View File

@ -22,6 +22,7 @@
export let label: IntlString | undefined = undefined
export let width: string | undefined = undefined
export let height: string | undefined = undefined
export let margin: string | undefined = undefined
export let value: string | undefined = undefined
export let placeholder: IntlString = plugin.string.EditBoxPlaceholder
export let placeholderParam: any | undefined = undefined
@ -40,7 +41,7 @@
}
</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}
<textarea
bind:value

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
export let fill: string = 'currentColor'
</script>
<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 Breadcrumbs } from './components/Breadcrumbs.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 NavItem } from './components/NavItem.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 IconDescription } from './components/icons/Description.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 Panel } from './components/Panel.svelte'

View File

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

View File

@ -164,4 +164,6 @@ export const settingsSeparators: DefSeparators = [
{ 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[]>([])

View File

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

View File

@ -85,6 +85,11 @@
"ConfigurationDisabled": "Disabled",
"ConfigDisable": "Disable",
"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": "Выключено",
"ConfigDisable": "Выключить",
"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.
-->
<script lang="ts">
import core, {
AnyAttribute,
ArrOf,
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 core, { AnyAttribute, Class, ClassifierKind, Doc, Ref, Space } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import {
Action,
ActionIcon,
AnySvelteComponent,
ButtonIcon,
Icon,
IconAdd,
IconDelete,
IconEdit,
IconMoreV2,
Label,
Menu,
getEventPositionElement,
showPopup,
IconSettings,
IconOpenedArrow
ModernButton
} from '@hcengineering/ui'
import { getContextActions } from '@hcengineering/view-resources'
import { ObjectPresenter } from '@hcengineering/view-resources'
import settings from '../plugin'
import CreateAttribute from './CreateAttribute.svelte'
import ClassAttributesList from './ClassAttributesList.svelte'
import EditAttribute from './EditAttribute.svelte'
import EditClassLabel from './EditClassLabel.svelte'
import { settingsStore, clearSettingsStore } from '../store'
import TypesPopup from './typeEditors/TypesPopup.svelte'
import { onDestroy } from 'svelte'
export let _class: Ref<Class<Doc>>
export let ofClass: Ref<Class<Doc>> | undefined = undefined
export let useOfClassAttributes = true
export let showTitle = true
export let showHierarchy: boolean = false
export let showTitle: boolean = !showHierarchy
export let attributeMapper:
| {
@ -72,139 +57,65 @@
const classQuery = createQuery()
let clazz: Class<Doc> | undefined
let hovered: number | null = null
let selected: number | null = null
let btnAdd: ButtonIcon
let classes: Class<Doc>[] = []
let clazzHierarchy: Class<Doc<Space>>
let selected: AnyAttribute | undefined = undefined
$: classQuery.query(core.class.Class, { _id: _class }, (res) => {
clazz = res.shift()
})
$: attributes = getCustomAttributes(_class)
function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] {
function hasCustomAttributes (_class: Ref<Class<Doc>>): boolean {
const cl = hierarchy.getClass(_class)
const attributes = Array.from(
hierarchy
.getAllAttributes(_class, _class === ofClass && useOfClassAttributes ? core.class.Doc : cl.extends)
.values()
hierarchy.getAllAttributes(_class, _class === ofClass ? core.class.Doc : cl.extends).values()
)
return attributes
}
const attrQuery = createQuery()
$: attrQuery.query(core.class.Attribute, { attributeOf: _class }, () => {
attributes = getCustomAttributes(_class)
})
function update (): void {
attributes = getCustomAttributes(_class)
return attributes.length > 0
}
export function createAttribute (ev: MouseEvent): void {
showPopup(TypesPopup, { _class }, getEventPositionElement(ev), (_id) => {
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 {
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 => {
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)
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>
{#if showTitle}
@ -222,11 +133,17 @@
{/if}
{/if}
<div class="hulyTableAttr-container">
<div class="hulyTableAttr-header font-medium-12">
<IconSettings size={'small'} />
<span><Label label={settings.string.ClassProperties} /></span>
<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'} />
<span><Label label={settings.string.ClassProperties} /></span>
{/if}
<ButtonIcon
bind:this={btnAdd}
kind={'primary'}
icon={IconAdd}
size={'small'}
@ -235,63 +152,50 @@
}}
/>
</div>
{#if attributes.length}
<div class="hulyTableAttr-content">
{#each attributes as attr, i}
{@const attrType = getAttrType(attr.type)}
<!-- 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>
{#if attr.isCustom}
<div class="hulyChip-item font-medium-12">
<Label label={settings.string.Custom} />
</div>
{/if}
{#if attr.icon !== undefined}
<div class="hulyTableAttr-content__row-icon">
<Icon icon={attr.icon} size={'small'} />
</div>
{/if}
<div class="hulyTableAttr-content__row-label font-regular-14" class:accent={!attr.hidden}>
<Label label={attr.label} />
</div>
{#if attributeMapper}
<svelte:component this={attributeMapper.component} {...attributeMapper.props} attribute={attr} />
{/if}
<div class="hulyTableAttr-content__row-type font-medium-12">
<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}
</div>
<div class="hulyTableAttr-content__row-arrow">
<IconOpenedArrow size={'small'} />
</div>
{#if showHierarchy}
<div class="hulyTableAttr-content class withTitle">
<div class="hulyTableAttr-content__title">
<Label label={settings.string.Properties} />
</div>
<div class="hulyTableAttr-content__wrapper">
<ClassAttributesList
{_class}
{ofClass}
{attributeMapper}
{selected}
on:deselect={handleDeselect}
on:select={handleSelect}
/>
</div>
</div>
{#each classes as clazz2}
<div class="hulyTableAttr-content class withTitle">
<div class="hulyTableAttr-content__title">
<Label label={clazz2.label} />
</div>
{/each}
<div class="hulyTableAttr-content__wrapper">
<ClassAttributesList
_class={clazz2._id}
{ofClass}
{attributeMapper}
{selected}
notUseOfClass
on:deselect={handleDeselect}
on:select={handleSelect}
/>
</div>
</div>
{/each}
{:else if hasCustomAttributes(_class)}
<div class="hulyTableAttr-content class">
<ClassAttributesList
{_class}
{ofClass}
{attributeMapper}
{selected}
on:deselect={handleDeselect}
on:select={handleSelect}
/>
</div>
{/if}
</div>
@ -306,114 +210,4 @@
line-height: 2rem;
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>

View File

@ -13,50 +13,28 @@
// limitations under the License.
-->
<script lang="ts">
import core, {
AnyAttribute,
ArrOf,
AttachedDoc,
Class,
ClassifierKind,
Collection,
Doc,
EnumOf,
Ref,
RefTo,
Type
} from '@hcengineering/core'
import { createEventDispatcher } from 'svelte'
import core, { AnyAttribute, ArrOf, AttachedDoc, Class, Collection, Doc, Ref, RefTo, Type } from '@hcengineering/core'
import { IntlString, getResource } from '@hcengineering/platform'
import presentation, { MessageBox, createQuery, getClient } from '@hcengineering/presentation'
import {
Action,
ActionIcon,
AnySvelteComponent,
CircleButton,
Icon,
IconAdd,
IconDelete,
IconEdit,
IconMoreV2,
Label,
Menu,
getEventPositionElement,
getEventPopupPositionElement,
showPopup
} from '@hcengineering/ui'
import { getContextActions } from '@hcengineering/view-resources'
import settings from '../plugin'
import CreateAttribute from './CreateAttribute.svelte'
import ClassAttributeRow from './ClassAttributeRow.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 ofClass: Ref<Class<Doc>> | undefined = undefined
export let useOfClassAttributes = true
export let showTitle = true
export let showCreate = true
export let notUseOfClass: boolean = false
export let selected: AnyAttribute | undefined = undefined
export let attributeMapper:
| {
@ -66,12 +44,15 @@
}
| undefined = undefined
const dispatch = createEventDispatcher()
const client = getClient()
const hierarchy = client.getHierarchy()
const classQuery = createQuery()
let clazz: Class<Doc> | undefined
let hovered: number | null = null
$: classQuery.query(core.class.Class, { _id: _class }, (res) => {
clazz = res.shift()
@ -81,9 +62,7 @@
function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] {
const cl = hierarchy.getClass(_class)
const attributes = Array.from(
hierarchy
.getAllAttributes(_class, _class === ofClass && useOfClassAttributes ? core.class.Doc : cl.extends)
.values()
hierarchy.getAllAttributes(_class, _class === ofClass && !notUseOfClass ? core.class.Doc : cl.extends).values()
)
return attributes
}
@ -98,12 +77,6 @@
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> {
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 actions: Action[] = [
@ -133,7 +107,7 @@
label: presentation.string.Edit,
icon: IconEdit,
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 {
@ -172,116 +148,27 @@
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>
{#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}
{@const attrType = getAttrType(attr.type)}
<tr
class="antiTable-body__row"
on:contextmenu={(ev) => {
ev.preventDefault()
void showMenu(ev, attr)
<ClassAttributeRow
attribute={attr}
attributeType={attrType}
selected={selected && attr._id === selected._id}
hovered={hovered === i}
{attributeMapper}
clickMore={async (event) => {
event.preventDefault()
void showMenu(event, attr, i)
}}
>
<td>
{#if i === 0 && clazz?.label !== undefined}
<div class="trans-title">
<Label label={clazz.label} />
</div>
{/if}
</td>
<td>
<div class="antiTable-cells__firstCell whitespace-nowrap flex-row-center">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div id="context-menu" on:click={(ev) => showMenu(ev, attr)}>
<div class="p-1">
<IconMoreV2 size={'medium'} />
</div>
</div>
{#if attr.icon !== undefined}
<div class="p-1">
<Icon icon={attr.icon} size={'small'} />
</div>
{/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>
on:contextmenu={async (event) => {
event.preventDefault()
void showMenu(event, attr, i)
}}
on:click={async () => {
if (selected && selected._id === attr._id) dispatch('deselect')
else dispatch('select', attr)
}}
/>
{/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}
<div class="hulyComponent-content__container columns">
<div class="hulyComponent-content__column">
<div class="hulyComponent-content__navHeader">
<div class="hulyComponent-content__navHeader divide">
<div class="hulyComponent-content__navHeader-menu">
<ButtonIcon kind={'tertiary'} icon={IconDescription} size={'small'} inheritColor />
</div>
@ -134,13 +134,13 @@
</div>
<Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} />
<div class="hulyComponent-content__column content">
<div class="hulyComponent-content">
{#if _class !== undefined}
<Scroller>
<ClassAttributes {_class} {ofClass} {attributeMapper} {useOfClassAttributes} />
</Scroller>
{/if}
</div>
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content">
{#if _class !== undefined}
<ClassAttributes {_class} {ofClass} {attributeMapper} />
{/if}
</div>
</Scroller>
</div>
</div>
</div>

View File

@ -16,7 +16,7 @@
import { createEventDispatcher } from 'svelte'
import { PluginConfiguration } from '@hcengineering/core'
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'
export let visibleNav: boolean = true
@ -37,39 +37,41 @@
<Breadcrumb icon={setting.icon.Setting} label={setting.string.Configuration} size={'large'} isCurrent />
</Header>
<div class="hulyComponent-content__column content">
<div class="flex-row-center flex-wrap gap-around-4">
{#each $configurationStore.list as config}
{#if config.label}
<div class="cardBox flex-col clear-mins" class:enabled={config.enabled ?? true}>
<div class="flex-row-center">
<span class="mr-2">
<Icon icon={config.icon ?? IconInfo} size={'medium'} />
</span>
<span class="fs-title">
<Label label={config.label} />
</span>
</div>
{#if config.description}
<div class="my-3 flex-grow clear-mins">
<Label label={config.description} />
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="flex-row-center flex-wrap gap-around-4">
{#each $configurationStore.list as config}
{#if config.label}
<div class="cardBox flex-col clear-mins" class:enabled={config.enabled ?? true}>
<div class="flex-row-center">
<span class="mr-2">
<Icon icon={config.icon ?? IconInfo} size={'medium'} />
</span>
<span class="fs-title">
<Label label={config.label} />
</span>
</div>
{/if}
<div class="flex-between flex-row-center">
{#if config.beta}
<Label label={setting.string.ConfigBeta} />
{#if config.description}
<div class="my-3 flex-grow clear-mins">
<Label label={config.description} />
</div>
{/if}
<div class="flex-row-center flex-reverse flex-grow max-h-9">
<Button
label={config.enabled ?? true ? setting.string.ConfigDisable : setting.string.ConfigEnable}
size={'large'}
on:click={() => change(config, !(config.enabled ?? true))}
/>
<div class="flex-between flex-row-center">
{#if config.beta}
<Label label={setting.string.ConfigBeta} />
{/if}
<div class="flex-row-center flex-reverse flex-grow max-h-9">
<Button
label={config.enabled ?? true ? setting.string.ConfigDisable : setting.string.ConfigEnable}
size={'large'}
on:click={() => change(config, !(config.enabled ?? true))}
/>
</div>
</div>
</div>
</div>
{/if}
{/each}
</div>
{/if}
{/each}
</div>
</Scroller>
</div>
</div>

View File

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

View File

@ -18,7 +18,7 @@
import { EmployeePresenter, personByIdStore } from '@hcengineering/contact-resources'
import { AccountRole, SortingOrder, getCurrentAccount } from '@hcengineering/core'
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'
export let visibleNav: boolean = true
@ -65,32 +65,34 @@
<EditBox kind={'search-style'} focusIndex={1} bind:value={search} placeholder={presentation.string.Search} />
</Header>
<div class="hulyComponent-content__column content">
<div class="hulyComponent-content">
{#each accounts as account (account._id)}
{@const employee = $personByIdStore.get(account.person)}
{#if employee?.name?.includes(search)}
<div class="flex-row-center p-2 flex-no-shrink">
<div class="p-1 min-w-80">
{#if employee}
<EmployeePresenter value={employee} disabled={false} />
{:else}
{account.email}
{/if}
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content">
{#each accounts as account (account._id)}
{@const employee = $personByIdStore.get(account.person)}
{#if employee?.name?.includes(search)}
<div class="flex-row-center p-2 flex-no-shrink">
<div class="p-1 min-w-80">
{#if employee}
<EmployeePresenter value={employee} disabled={false} />
{:else}
{account.email}
{/if}
</div>
<DropdownLabelsIntl
label={setting.string.Role}
disabled={account.role > currentRole || (account.role === AccountRole.Owner && owners.length === 1)}
kind={'primary'}
size={'medium'}
{items}
selected={account.role?.toString()}
on:selected={(e) => {
void change(account, Number(e.detail))
}}
/>
</div>
<DropdownLabelsIntl
label={setting.string.Role}
disabled={account.role > currentRole || (account.role === AccountRole.Owner && owners.length === 1)}
kind={'primary'}
size={'medium'}
{items}
selected={account.role?.toString()}
on:selected={(e) => {
void change(account, Number(e.detail))
}}
/>
</div>
{/if}
{/each}
</div>
{/if}
{/each}
</div>
</Scroller>
</div>
</div>

View File

@ -78,6 +78,7 @@ export default mergeIds(settingId, setting, {
ConfigEnable: '' as IntlString,
ConfigBeta: '' 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
*/
export interface SettingsStore {
component?: ComponentType | null
props?: object | null
id?: any | undefined
component?: ComponentType | undefined
props?: object | undefined
}
/**
@ -33,5 +34,5 @@ export const settingsStore = writable<SettingsStore>({})
* @public
*/
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,
Owners: '' as IntlString,
Configure: '' as IntlString,
InviteSettings: '' as IntlString
InviteSettings: '' as IntlString,
Properties: '' as IntlString,
TaskTypes: '' as IntlString,
Automations: '' as IntlString,
Collections: '' as IntlString
},
icon: {
AccountSettings: '' as Asset,

View File

@ -84,6 +84,10 @@
"TaskType": "Task type",
"ManageProjects": "Project types",
"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": "Тип задачи",
"ManageProjects": "Управление проектами",
"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">
import { Ref } from '@hcengineering/core'
import { createQuery, hasResource } from '@hcengineering/presentation'
import { createQuery } from '@hcengineering/presentation'
import setting from '@hcengineering/setting'
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 descriptorId: Ref<ProjectTypeDescriptor> | undefined
@ -32,7 +32,7 @@
})
$: 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 {
@ -45,7 +45,7 @@
</div>
<div class="flex-col overflow-y-auto">
{#each categories as f (f._id)}
{#if hasResource(f.icon)}
{#if f.icon}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
@ -56,7 +56,7 @@
}}
>
<div class="icon flex-no-shrink mr-4">
<Component is={f.icon} />
<Icon icon={f.icon} size={'small'} />
</div>
<div class="flex-grow flex-col">
<div class="fs-title overflow-label">

View File

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

View File

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

View File

@ -16,23 +16,20 @@
<script lang="ts">
import { AttributeEditor, getClient } from '@hcengineering/presentation'
import task, { ProjectType, TaskType, calculateStatuses } from '@hcengineering/task'
import { Ref, Status } from '@hcengineering/core'
import { Asset, getEmbeddedLabel } from '@hcengineering/platform'
import { Label, showPopup } from '@hcengineering/ui'
import { IconPicker, statusStore } from '@hcengineering/view-resources'
import { ClassAttributes } from '@hcengineering/setting-resources'
import { taskTypeStore } from '../..'
import StatesProjectEditor from '../state/StatesProjectEditor.svelte'
import TaskTypeKindEditor from '../taskTypes/TaskTypeKindEditor.svelte'
import TaskTypeRefEditor from '../taskTypes/TaskTypeRefEditor.svelte'
import TaskTypeIcon from './TaskTypeIcon.svelte'
import TypeClassEditor from './TypeClassEditor.svelte'
export let projectType: ProjectType
export let taskType: TaskType
export let taskTypeCounter: Map<Ref<TaskType>, number>
export let taskTypes: TaskType[]
const client = getClient()
@ -56,101 +53,95 @@
}
</script>
<div class="p-4 flex-col">
<div class="flex-col">
<div class="flex-row-center flex-between">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="mb-1" on:click={selectIcon}>
<TaskTypeIcon value={taskType} size={'large'} />
</div>
<div class="ml-2 flex-row-center">
{taskTypeCounter.get(taskType._id) ?? 0}
</div>
<div class="flex-col">
<div class="flex-row-center flex-between">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="mb-1" on:click={selectIcon}>
<TaskTypeIcon value={taskType} size={'large'} />
</div>
<div class="flex-row-center">
<div class="fs-title mb-4">
<AttributeEditor
maxWidth={'10rem'}
_class={task.class.TaskType}
object={taskType}
key="name"
editKind={'large-style'}
/>
</div>
</div>
<TaskTypeKindEditor
kind={taskType.kind}
on:change={(evt) => {
void client.diffUpdate(taskType, { kind: evt.detail })
}}
/>
<div class="flex-row-center p-2 mt-4">
<div class="flex-no-shrink trans-title uppercase">
<Label label={getEmbeddedLabel('Parent type restrictions')} />
</div>
{#if taskType.kind === 'subtask' || taskType.kind === 'both'}
<TaskTypeRefEditor
label={getEmbeddedLabel('Allowed parents')}
value={taskType.allowedAsChildOf}
types={taskTypes.filter((it) => it.kind === 'task' || it.kind === 'both')}
onChange={(evt) => {
void client.diffUpdate(taskType, { allowedAsChildOf: evt })
}}
/>
{/if}
<div class="ml-2 flex-row-center">
{taskTypeCounter.get(taskType._id) ?? 0}
</div>
</div>
<div class="panelBox">
<div class="p-2 trans-title">
<Label label={getEmbeddedLabel('Process')} />
</div>
<div class="ml-2">
<StatesProjectEditor
{taskType}
type={projectType}
{states}
on:delete={async (evt) => {
const index = taskType.statuses.findIndex((p) => p === evt.detail.state._id)
taskType.statuses.splice(index, 1)
await client.update(taskType, {
statuses: taskType.statuses
})
await client.update(projectType, {
statuses: calculateStatuses(projectType, $taskTypeStore, [
{ taskTypeId: taskType._id, statuses: taskType.statuses }
])
})
}}
on:move={async (evt) => {
const index = taskType.statuses.findIndex((p) => p === evt.detail.stateID)
const state = taskType.statuses.splice(index, 1)[0]
const statuses = [
...taskType.statuses.slice(0, evt.detail.position),
state,
...taskType.statuses.slice(evt.detail.position)
]
await client.update(taskType, {
statuses
})
await client.update(projectType, {
statuses: calculateStatuses(projectType, $taskTypeStore, [{ taskTypeId: taskType._id, statuses }])
})
}}
<div class="flex-row-center">
<div class="fs-title mb-4">
<AttributeEditor
maxWidth={'10rem'}
_class={task.class.TaskType}
object={taskType}
key="name"
editKind={'large-style'}
/>
</div>
</div>
<div class="panelBox flex flex-col">
<div class="ml-2">
<TypeClassEditor ofClass={taskType.ofClass} _class={taskType.targetClass} />
<TaskTypeKindEditor
kind={taskType.kind}
on:change={(evt) => {
void client.diffUpdate(taskType, { kind: evt.detail })
}}
/>
<div class="flex-row-center p-2 mt-4">
<div class="flex-no-shrink trans-title uppercase">
<Label label={getEmbeddedLabel('Parent type restrictions')} />
</div>
{#if taskType.kind === 'subtask' || taskType.kind === 'both'}
<TaskTypeRefEditor
label={getEmbeddedLabel('Allowed parents')}
value={taskType.allowedAsChildOf}
types={taskTypes.filter((it) => it.kind === 'task' || it.kind === 'both')}
onChange={(evt) => {
void client.diffUpdate(taskType, { allowedAsChildOf: evt })
}}
/>
{/if}
</div>
</div>
<div class="panelBox">
<div class="p-2 trans-title">
<Label label={getEmbeddedLabel('Process')} />
</div>
<div class="ml-2">
<StatesProjectEditor
{taskType}
type={projectType}
{states}
on:delete={async (evt) => {
const index = taskType.statuses.findIndex((p) => p === evt.detail.state._id)
taskType.statuses.splice(index, 1)
await client.update(taskType, {
statuses: taskType.statuses
})
await client.update(projectType, {
statuses: calculateStatuses(projectType, $taskTypeStore, [
{ taskTypeId: taskType._id, statuses: taskType.statuses }
])
})
}}
on:move={async (evt) => {
const index = taskType.statuses.findIndex((p) => p === evt.detail.stateID)
const state = taskType.statuses.splice(index, 1)[0]
const statuses = [
...taskType.statuses.slice(0, evt.detail.position),
state,
...taskType.statuses.slice(evt.detail.position)
]
await client.update(taskType, {
statuses
})
await client.update(projectType, {
statuses: calculateStatuses(projectType, $taskTypeStore, [{ taskTypeId: taskType._id, statuses }])
})
}}
/>
</div>
</div>
<ClassAttributes ofClass={taskType.ofClass} _class={taskType.targetClass} showHierarchy />
<style lang="scss">
.row {

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,
TaskCreated: '' as IntlString,
CreateProjectType: '' as IntlString,
ClassicProject: '' as IntlString
ClassicProject: '' as IntlString,
LastSave: '' as IntlString,
Published: '' as IntlString,
CountProjects: '' as IntlString,
ProjectTypeTitle: '' as IntlString
},
status: {
AssigneeRequired: '' as IntlString

View File

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

View File

@ -18,7 +18,8 @@
Breadcrumb,
Separator,
defineSeparators,
settingsSeparators
settingsSeparators,
Scroller
} from '@hcengineering/ui'
import { getActions as getContributedActions, TreeItem, TreeNode } from '@hcengineering/view-resources'
import templatesPlugin from '../plugin'
@ -195,79 +196,81 @@
</div>
<Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} />
<div class="hulyComponent-content__column content">
<div class="hulyComponent-content">
{#if newTemplate}
<div class="flex-between mr-4">
<span class="trans-title mb-3">
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
<div class="hulyComponent-content">
{#if newTemplate}
<div class="flex-between mr-4">
<span class="trans-title mb-3">
{#if mode === Mode.Create}
<Label label={templatesPlugin.string.CreateTemplate} />
{:else if mode === Mode.Edit}
<Label label={templatesPlugin.string.EditTemplate} />
{:else}
<Label label={templatesPlugin.string.ViewTemplate} />
{/if}
</span>
{#if mode === Mode.Create}
<Label label={templatesPlugin.string.CreateTemplate} />
{:else if mode === Mode.Edit}
<Label label={templatesPlugin.string.EditTemplate} />
{:else}
<Label label={templatesPlugin.string.ViewTemplate} />
<SpaceSelector
_class={templatesPlugin.class.TemplateCategory}
label={templatesPlugin.string.TemplateCategory}
bind:space
create={{
component: templatesPlugin.component.CreateTemplateCategory,
label: templatesPlugin.string.CreateTemplateCategory
}}
/>
{/if}
</span>
{#if mode === Mode.Create}
<SpaceSelector
_class={templatesPlugin.class.TemplateCategory}
label={templatesPlugin.string.TemplateCategory}
bind:space
create={{
component: templatesPlugin.component.CreateTemplateCategory,
label: templatesPlugin.string.CreateTemplateCategory
}}
/>
{/if}
</div>
<div class="text-lg caption-color">
</div>
<div class="text-lg caption-color">
{#if mode !== Mode.View}
<EditBox bind:value={newTemplate.title} placeholder={templatesPlugin.string.TemplatePlaceholder} />
{:else}
{newTemplate.title}
{/if}
</div>
<div class="separator" />
{#if mode !== Mode.View}
<EditBox bind:value={newTemplate.title} placeholder={templatesPlugin.string.TemplatePlaceholder} />
<StyledTextEditor bind:content={newTemplate.message} bind:this={textEditor} on:value={updateTemplate}>
<div class="flex flex-reverse flex-grow">
<div class="ml-2">
<Button
disabled={newTemplate.title.trim().length === 0}
kind={'primary'}
label={templatesPlugin.string.SaveTemplate}
on:click={saveNewTemplate}
/>
</div>
<div class="ml-2">
<Button
label={templatesPlugin.string.Cancel}
on:click={() => {
if (mode === Mode.Create) {
newTemplate = undefined
}
mode = Mode.View
}}
/>
</div>
<Button label={templatesPlugin.string.Field} on:click={addField} />
</div>
</StyledTextEditor>
{:else}
{newTemplate.title}
{/if}
</div>
<div class="separator" />
{#if mode !== Mode.View}
<StyledTextEditor bind:content={newTemplate.message} bind:this={textEditor} on:value={updateTemplate}>
<div class="flex flex-reverse flex-grow">
<div class="ml-2">
<Button
disabled={newTemplate.title.trim().length === 0}
kind={'primary'}
label={templatesPlugin.string.SaveTemplate}
on:click={saveNewTemplate}
/>
</div>
<div class="ml-2">
<Button
label={templatesPlugin.string.Cancel}
on:click={() => {
if (mode === Mode.Create) {
newTemplate = undefined
}
mode = Mode.View
}}
/>
</div>
<Button label={templatesPlugin.string.Field} on:click={addField} />
<div class="text">
<MessageViewer message={newTemplate.message} />
</div>
</StyledTextEditor>
{:else}
<div class="text">
<MessageViewer message={newTemplate.message} />
</div>
<div class="flex flex-reverse">
<Button
kind={'primary'}
label={templatesPlugin.string.EditTemplate}
on:click={() => {
mode = Mode.Edit
}}
/>
</div>
<div class="flex flex-reverse">
<Button
kind={'primary'}
label={templatesPlugin.string.EditTemplate}
on:click={() => {
mode = Mode.Edit
}}
/>
</div>
{/if}
{/if}
{/if}
</div>
</div>
</Scroller>
</div>
</div>
</div>