Update Settings layout (#4277)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2023-12-27 18:35:31 +03:00 committed by GitHub
parent 09579fd2b0
commit f4d6faf4f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 2327 additions and 644 deletions

View File

@ -565,7 +565,7 @@ export function createModel (builder: Builder): void {
name: 'relations',
label: tracker.string.RelatedIssues,
icon: tracker.icon.Relations,
component: tracker.component.EditRelatedTargets,
component: tracker.component.SettingsRelatedTargets,
group: 'settings-editor',
secured: false,
order: 4000

View File

@ -59,6 +59,7 @@ export default mergeIds(trackerId, tracker, {
MilestoneFilter: '' as AnyComponent,
EditRelatedTargets: '' as AnyComponent,
EditRelatedTargetsPopup: '' as AnyComponent,
SettingsRelatedTargets: '' as AnyComponent,
IssueSearchIcon: '' as AnyComponent,
MembersArrayEditor: '' as AnyComponent
},

View File

@ -83,6 +83,20 @@
// New
--global-accent-IconColor: #6796FF;
--button-accent-LabelColor: #fff;
--button-disabled-LabelColor: #8b97ad;
--button-accent-IconColor: #fff;
--button-disabled-IconColor: #8b97ad;
--button-primary-BackgroundColor: #3364e2;
--button-primary-BorderColor: #d1d5de1a;
--button-primary-hover-BackgroundColor: #6191fe;
--button-primary-active-BackgroundColor: #2553cf;
--button-primary-loading-LabelColor: #6191fe;
--button-negative-loading-LabelColor: #ff9187;
--button-negative-BorderColor: #d1d5de26;
--button-negative-hover-BackgroundColor: #e34748;
--button-negative-active-BackgroundColor: #c42a32;
}
/* Dark Theme */
@ -317,12 +331,42 @@
--global-ui-hover-BackgroundColor: #A5BDFF1A;
--global-ui-highlight-BackgroundColor: #A5BDFF0D;
--global-ui-hover-highlight-BackgroundColor: #A5BDFF26;
--global-surface-01-BackgroundColor: #131925;
--global-surface-01-BorderColor: #1F2737;
--global-surface-02-BackgroundColor: #19202E;
--global-surface-02-BorderColor: #262F40;
--global-surface-03-hover-BackgroundColor: #19202E;
--global-primary-LinkColor: #4D7FF5;
--global-primary-TextColor: #FFFFFF;
--global-secondary-TextColor: #C1C9D6;
--global-tertiary-TextColor: #8E99AF;
--global-accent-TextColor: #4D7FF5;
/** Buttons **/
--button-subtle-LabelColor: #fff;
--button-subtle-IconColor: #fff;
--button-disabled-BackgroundColor: #d1d5de0d;
--button-primary-loading-LabelColor: #6191fe;
--button-secondary-BackgroundColor: #d1d5de0d;
--button-secondary-BorderColor: #d1d5de1a;
--button-secondary-hover-BackgroundColor: #d1d5de1a;
--button-secondary-active-BackgroundColor: #d1d5de26;
--button-negative-BackgroundColor: #e34748;
--button-tertiary-hover-BackgroundColor: #d1d5de1a;
--button-tertiary-active-BackgroundColor: #d1d5de26;
--button-menu-active-BorderColor: #d9dee6;
/** Editbox **/
--input-BackgroundColor: #a5bdff0d;
--input-hover-BackgroundColor: #a5bdff1a;
--input-BorderColor: #a5bdff0d;
--input-TextColor: #ffffff;
--input-LabelColor: #ffffff;
--input-filled-LabelColor: #8b97ad;
--input-PlaceholderColor: #8b97ad;
--input-hover-PlaceholderColor: #ffffff;
--input-focus-PlaceholderColor: #556178;
--input-HelperColor: #8b97ad;
--input-error-BorderColor: #fb6863;
--input-search-IconColor: #ffffff;
}
/* Light Theme */
@ -557,10 +601,40 @@
--global-ui-hover-BackgroundColor: #1530721A;
--global-ui-highlight-BackgroundColor: #A5BDFF26;
--global-ui-hover-highlight-BackgroundColor: #A5BDFF40;
--global-surface-01-BackgroundColor: #F8F9FA;
--global-surface-01-BorderColor: #DDE1E9;
--global-surface-02-BackgroundColor: #19202E;
--global-surface-02-BorderColor: #EBEEF2;
--global-surface-03-hover-BackgroundColor: #F8F9FA;
--global-primary-LinkColor: #3566E2;
--global-primary-TextColor: #0F121A;
--global-secondary-TextColor: #5A667E;
--global-tertiary-TextColor: #7B879E;
--global-accent-TextColor: #3566E2;
/** Buttons **/
--button-subtle-LabelColor: #000;
--button-subtle-IconColor: #000;
--button-disabled-BackgroundColor: #1725470d;
--button-primary-loading-LabelColor: #95baff;
--button-secondary-BackgroundColor: #1725470d;
--button-secondary-BorderColor: #1725471a;
--button-secondary-hover-BackgroundColor: #1725471a;
--button-secondary-active-BackgroundColor: #17254726;
--button-negative-BackgroundColor: #ea4c4c;
--button-tertiary-hover-BackgroundColor: #1725471a;
--button-tertiary-active-BackgroundColor: #17254726;
--button-menu-active-BorderColor: #0f121a;
/** Editbox **/
--input-BackgroundColor: #1530720d;
--input-hover-BackgroundColor: #1530721a;
--input-BorderColor: #1530720d;
--input-TextColor: #0f121a;
--input-LabelColor: #0f121a;
--input-filled-LabelColor: #556178;
--input-PlaceholderColor: #556178;
--input-hover-PlaceholderColor: #0f121a;
--input-focus-PlaceholderColor: #8b97ad;
--input-HelperColor: #556178;
--input-error-BorderColor: #e34748;
--input-search-IconColor: #0f121a;
}

View File

@ -574,6 +574,7 @@ input.search {
.py-0-5 { padding: 0.125rem 0; }
.py-1 { padding: 0.25rem 0; }
.py-2 { padding: 0.5rem 0; }
.py-3 { padding: 0.75rem 0; }
.py-4 { padding: 1rem 0; }
.py-8 { padding: 2rem 0; }
.py-10 { padding: 2.5rem 0; }

View File

@ -0,0 +1,59 @@
//
// 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.
//
* {
/** Space & Dimensions **/
--spacing-0_25: 0.125rem;
--spacing-0_5: 0.25rem;
--spacing-0_75: 0.375rem;
--spacing-1: 0.5rem;
--spacing-1_5: 0.75rem;
--spacing-1_75: 0.875rem;
--spacing-2: 1rem;
--spacing-2_5: 1.25rem;
--spacing-3: 1.5rem;
--spacing-3_5: 1.75rem;
--spacing-4: 2rem;
--spacing-4_5: 2.25rem;
--spacing-5: 2.5rem;
--spacing-6: 3rem;
--spacing-6_5: 3.5rem;
--spacing-7: 4rem;
--spacing-8: 5rem;
--spacing-9: 6rem;
--spacing-10: 7.5rem;
/** UI Elements Size **/
--global-min-Size: 1rem;
--global-extra-small-Size: 1.5rem;
--global-small-Size: 2rem;
--global-medium-Size: 2.5rem;
--global-large-Size: 3rem;
--global-extra-large-Size: 3.5rem;
--global-max-Size: 4rem;
/** Border Radius **/
--extra-small-BorderRadius: 0.25rem;
--extra-small-focus-BorderRadius: 0.375rem;
--small-BorderRadius: 0.375rem;
--small-focus-BorderRadius: 0.5rem;
--medium-BorderRadius: 0.5rem;
--medium-focus-BorderRadius: 0.625rem;
--large-BorderRadius: 1rem;
--large-focus-BorderRadius: 1.125rem;
}

View File

@ -18,7 +18,9 @@
.font-medium-12,
.font-regular-14,
.font-medium-14,
.font-bold-14 {
.font-bold-14,
.paragraph-regular-14,
.heading-medium-16 {
font-family: var(--font-family);
font-style: normal;
line-height: 1rem;
@ -30,11 +32,13 @@
}
.font-regular-14,
.font-medium-14,
.font-bold-14 {
.font-bold-14,
.paragraph-regular-14 {
font-size: 0.875rem;
}
.font-regular-12,
.font-regular-14 {
.font-regular-14,
.paragraph-regular-14 {
font-weight: 400;
}
.font-medium-12,
@ -44,7 +48,15 @@
.font-bold-14 {
font-weight: 700;
}
.heading-medium-16 {
font-size: 1rem;
font-weight: 500;
line-height: 1.125rem;
}
.paragraph-regular-14 {
line-height: 1.25rem;
color: var(--global-tertiary-TextColor);
}
/* Panels */
* {
@ -83,7 +95,7 @@
&.header { background-color: var(--theme-comp-header-color); }
&.filled { background-color: var(--theme-bg-color); }
&.filledNav { background-color: var(--theme-navpanel-color); }
&.filledNav { background-color: var(--theme-navpanel-color) !important; }
&.border-left { border-left: 1px solid var(--theme-divider-color); }
&.border-right { border-right: 1px solid var(--theme-divider-color); }
}

View File

@ -13,6 +13,74 @@
// limitations under the License.
//
/* Huly Component */
.hulyComponent {
display: flex;
flex-direction: column;
flex-grow: 1;
height: 100%;
min-height: 0;
min-width: 0;
background-color: var(--theme-panel-color); // var(--global-surface-02-BackgroundColor);
border: 1px solid var(--theme-divider-color); // var(--global-surface-02-BorderColor);
border-radius: var(--small-focus-BorderRadius);
}
.hulyComponent-content,
.hulyComponent-content__container,
.hulyComponent-content__column,
.hulyComponent-content__column.content,
.hulyComponent-content__navHeader {
display: flex;
width: 100%;
min-width: 0;
min-height: 0;
}
.hulyComponent-content {
flex-shrink: 0;
max-width: 64rem;
&__container {
height: 100%;
}
&:not(.columns) {
flex-direction: column;
}
&__column {
flex-direction: column;
height: 100%;
&.navigation .hulyNavItem-container,
.hulyNavItem-container {
margin: 0 0.75rem;
}
&.content {
overflow-y: auto;
flex-direction: column;
align-items: center;
padding: var(--spacing-3);
}
}
&__navHeader {
flex-direction: column;
flex-shrink: 0;
padding-bottom: var(--spacing-3);
border-bottom: 1px solid var(--theme-navpanel-divider);
&-menu {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
padding: var(--spacing-2);
width: var(--global-extra-large-Size);
height: var(--global-extra-large-Size);
}
&-hint {
margin: var(--spacing-0_25) var(--spacing-3) 0 var(--spacing-2);
}
}
}
/* Component */
.antiComponent {
display: flex;

View File

@ -13,6 +13,7 @@
// limitations under the License.
//
@import "./_vars.scss";
@import "./_colors.scss";
@import "./_layouts.scss";
@import "./common.scss";

View File

@ -0,0 +1,85 @@
<!--
// 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">
import type { Asset, IntlString } from '@hcengineering/platform'
import { AnySvelteComponent } from '../types'
import { ComponentType } from 'svelte'
import Icon from './Icon.svelte'
import Label from './Label.svelte'
export let icon: Asset | AnySvelteComponent | ComponentType | undefined = undefined
export let label: IntlString | undefined = undefined
export let title: string | undefined = undefined
export let size: 'large' | 'small'
export let isCurrent: boolean = false
</script>
<button class="hulyBreadcrumb-container {size}" class:current={isCurrent} on:click>
{#if size === 'large' && icon}
<div class="hulyBreadcrumb-avatar">
<Icon {icon} size={'small'} />
</div>
{/if}
<span class="{size === 'large' ? 'heading-medium-16' : 'font-regular-14'} hulyBreadcrumb-label overflow-label">
{#if label}
<Label {label} />
{:else if title}
{title}
{/if}
</span>
</button>
<style lang="scss">
.hulyBreadcrumb-container {
display: flex;
align-items: center;
gap: var(--spacing-0_75);
margin: 0;
padding: 0 var(--spacing-1);
min-width: 0;
border: none;
outline: none;
.hulyBreadcrumb-avatar {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
padding: var(--spacing-0_5);
width: var(--global-extra-small-Size);
height: var(--global-extra-small-Size);
color: var(--global-secondary-TextColor);
background-color: var(--global-ui-BackgroundColor);
border-radius: var(--extra-small-BorderRadius);
}
.hulyBreadcrumb-label {
color: var(--global-secondary-TextColor);
}
&.current .hulyBreadcrumb-label {
font-weight: 700;
}
&:not(.current) {
cursor: pointer;
}
&:hover {
.hulyBreadcrumb-avatar {
background-color: var(--global-ui-hover-BackgroundColor);
}
.hulyBreadcrumb-label {
color: var(--global-primary-LinkColor);
}
}
}
</style>

View File

@ -0,0 +1,75 @@
<!--
// 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">
import { createEventDispatcher, ComponentType } from 'svelte'
import type { Asset, IntlString } from '@hcengineering/platform'
import { AnySvelteComponent } from '../types'
import ChevronRight from './icons/ChevronRight.svelte'
import Label from './Label.svelte'
import Breadcrumb from './Breadcrumb.svelte'
interface BreadcrumbItem {
icon?: Asset | AnySvelteComponent | ComponentType
label?: IntlString
title?: string
}
export let items: BreadcrumbItem[]
export let afterLabel: IntlString | undefined = undefined
export let size: 'large' | 'small' = 'large'
export let selected: number | null = null
const dispatch = createEventDispatcher()
</script>
<div class="hulyBreadcrumbs-container {size}">
{#each items as item, i}
{#if i !== 0}<ChevronRight size={'small'} />{/if}
<Breadcrumb
{...item}
{size}
isCurrent={selected === i}
on:click={() => {
if (selected !== i) dispatch('select', i)
}}
/>
{/each}
{#if afterLabel}
<span class="hulyBreadcrumbs-afterLabel">
<Label label={afterLabel} />
</span>
{/if}
</div>
<style lang="scss">
.hulyBreadcrumbs-container {
display: flex;
align-items: center;
height: var(--global-small-Size);
min-width: 0;
.hulyBreadcrumbs-afterLabel {
white-space: nowrap;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
padding: var(--spacing-0_25) var(--spacing-0_5);
text-transform: uppercase;
background-color: var(--global-ui-hover-BackgroundColor);
color: var(--global-secondary-TextColor);
border-radius: 0.25rem;
}
}
</style>

View File

@ -0,0 +1,304 @@
<!--
// 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">
import type { Asset, IntlString } from '@hcengineering/platform'
import { AnySvelteComponent } from '../types'
import { ComponentType } from 'svelte'
import Spinner from './Spinner.svelte'
import Icon from './Icon.svelte'
import Label from './Label.svelte'
export let title: string | undefined = undefined
export let label: IntlString | undefined = undefined
export let icon: Asset | AnySvelteComponent | ComponentType | undefined = undefined
export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative'
export let size: 'large' | 'medium' | 'small'
export let disabled: boolean = false
export let loading: boolean = false
export let pressed: boolean = false
export let hasMenu: boolean = false
export let type: 'type-button' | 'type-button-icon'
export let inheritColor: boolean = false
</script>
<button
class="font-medium-14 {kind} {size} {type}"
class:loading
class:pressed
class:inheritColor
class:menu={hasMenu}
disabled={loading || disabled}
on:click
>
{#if loading}
<div class="icon animate"><Spinner size={'small'} /></div>
{:else if icon}<div class="icon"><Icon {icon} size={'small'} /></div>{/if}
{#if title}<span>{title}</span>{/if}
{#if label}<span><Label {label} /></span>{/if}
</button>
<style lang="scss">
button {
display: inline-flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
gap: var(--spacing-1);
border: 1px;
border-style: solid;
&:not(:disabled, .loading) {
cursor: pointer;
}
.icon {
width: var(--spacing-2_5);
height: var(--spacing-2_5);
&.animate {
animation: rotate 2s linear infinite;
}
}
&:focus {
outline: 2px solid var(--global-focus-BorderColor);
outline-offset: 2px;
}
&.type-button-icon {
padding: 0;
}
&.large {
height: var(--spacing-6);
border-radius: var(--medium-BorderRadius);
&.type-button {
padding: 0 var(--spacing-2);
}
&.type-button-icon {
width: var(--spacing-6);
}
}
&.medium {
height: var(--spacing-5);
border-radius: var(--medium-BorderRadius);
&.type-button {
padding: 0 var(--spacing-2);
}
&.type-button-icon {
width: var(--spacing-5);
}
}
&.small {
height: var(--spacing-4);
border-radius: var(--small-BorderRadius);
&.type-button {
padding: 0 var(--spacing-1_5);
}
&.type-button-icon {
width: var(--spacing-4);
}
}
&.type-button-icon .icon {
width: var(--spacing-2);
height: var(--spacing-2);
}
&.primary {
border-color: var(--button-primary-BorderColor);
background-color: var(--button-primary-BackgroundColor);
.icon {
fill: var(--button-accent-IconColor);
}
span {
color: var(--button-accent-LabelColor);
}
&:hover {
background-color: var(--button-primary-hover-BackgroundColor);
}
&:active,
&.pressed {
background-color: var(--button-primary-active-BackgroundColor);
}
&.menu:enabled:active,
&.pressed {
border-color: var(--button-menu-active-BorderColor);
}
&:disabled:not(.loading) {
background-color: var(--button-disabled-BackgroundColor);
border-color: transparent;
cursor: not-allowed;
.icon {
fill: var(--button-disabled-IconColor);
}
span {
color: var(--button-disabled-LabelColor);
}
}
&.loading {
background-color: var(--button-primary-active-BackgroundColor);
span {
color: var(--button-primary-loading-LabelColor);
}
}
}
&.secondary {
border-color: var(--button-secondary-BorderColor);
background-color: var(--button-secondary-BackgroundColor);
.icon {
fill: var(--button-subtle-IconColor);
}
span {
color: var(--button-subtle-LabelColor);
}
&:hover {
background-color: var(--button-secondary-hover-BackgroundColor);
}
&:active,
&.pressed {
background-color: var(--button-secondary-active-BackgroundColor);
}
&.menu:enabled:active,
&.pressed {
border-color: var(--button-menu-active-BorderColor);
}
&:disabled:not(.loading) {
background-color: var(--button-disabled-BackgroundColor);
border-color: transparent;
cursor: not-allowed;
.icon {
fill: var(--button-disabled-IconColor);
}
span {
color: var(--button-disabled-LabelColor);
}
}
&.loading {
background-color: var(--button-secondary-active-BackgroundColor);
span {
color: var(--button-disabled-LabelColor);
}
}
}
&.tertiary {
border-color: transparent;
background-color: transparent;
&:not(.inheritColor) .icon {
fill: var(--button-subtle-IconColor);
}
&.inheritColor {
color: inherit;
.icon {
fill: currentColor;
}
}
span {
color: var(--button-subtle-LabelColor);
}
&:hover:enabled {
background-color: var(--button-tertiary-hover-BackgroundColor);
}
&:active:enabled,
&.pressed:enabled {
background-color: var(--button-tertiary-active-BackgroundColor);
}
&.menu:active:enabled,
&.pressed:enabled {
border-color: var(--button-menu-active-BorderColor);
}
&:disabled:not(.loading) {
border-color: transparent;
cursor: not-allowed;
.icon {
fill: var(--button-disabled-IconColor);
}
span {
color: var(--button-disabled-LabelColor);
}
}
&.loading {
background-color: var(--button-tertiary-active-BackgroundColor);
span {
color: var(--button-disabled-LabelColor);
}
}
}
&.negative {
border-color: var(--button-negative-BorderColor);
background-color: var(--button-negative-BackgroundColor);
.icon {
fill: var(--button-accent-IconColor);
}
span {
color: var(--button-accent-LabelColor);
}
&:hover {
background-color: var(--button-negative-hover-BackgroundColor);
}
&:active,
&.pressed {
background-color: var(--button-negative-active-BackgroundColor);
}
&.menu:enabled:active,
&.pressed {
border-color: var(--button-menu-active-BorderColor);
}
&:disabled:not(.loading) {
background-color: var(--button-disabled-BackgroundColor);
border-color: transparent;
cursor: not-allowed;
.icon {
fill: var(--button-disabled-IconColor);
}
span {
color: var(--button-disabled-LabelColor);
}
}
&.loading {
background-color: var(--button-negative-active-BackgroundColor);
span {
color: var(--button-negative-loading-LabelColor);
}
}
}
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
</style>

View File

@ -0,0 +1,29 @@
<!--
// 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">
import type { Asset } from '@hcengineering/platform'
import { AnySvelteComponent } from '../types'
import { ComponentType } from 'svelte'
import ButtonBase from './ButtonBase.svelte'
export let kind: 'primary' | 'secondary' | 'tertiary' | 'negative' = 'secondary'
export let size: 'large' | 'medium' | 'small' = 'large'
export let icon: Asset | AnySvelteComponent | ComponentType
export let disabled: boolean = false
export let loading: boolean = false
export let inheritColor: boolean = false
</script>
<ButtonBase type={'type-button-icon'} {kind} {size} {icon} {disabled} {loading} {inheritColor} on:click />

View File

@ -0,0 +1,52 @@
<!--
// 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">
export let isOpen: boolean
export let empty: boolean = false
export let level: number = 1
</script>
<div class="hulyFold-container" class:opened={isOpen && !empty} style:margin-left={`${(level - 1) * 1.5}rem`}>
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
{#if empty}
<path
d="M8 8.99988C8.55228 8.99988 9 8.55216 9 7.99988C9 7.44759 8.55228 6.99988 8 6.99988C7.44772 6.99988 7 7.44759 7 7.99988C7 8.55216 7.44772 8.99988 8 8.99988Z"
/>
{:else}
<path d="M11 8L5.99995 3L5.29285 3.70711L9.58574 8L5.29285 12.2929L5.99995 13L11 8Z" />
{/if}
</svg>
</div>
<style lang="scss">
.hulyFold-container {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
padding: 0.25rem;
width: 1.5rem;
height: 1.5rem;
border-radius: 0.25rem;
color: var(--button-disabled-IconColor);
transform-origin: center;
transform: rotate(0deg);
transition: transform 0.15s ease-in-out;
&.opened {
transform: rotate(90deg);
}
}
</style>

View File

@ -0,0 +1,89 @@
<!--
// 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">
import { createEventDispatcher } from 'svelte'
import { IconMaximize, IconMinimize } from '..'
export let minimize: boolean = false
const dispatch = createEventDispatcher()
</script>
<div class="hulyHeader-container">
<button class="hulyHeader-button" on:click={() => dispatch('resize', minimize)}>
{#if minimize}
<IconMinimize size={'small'} />
{:else}
<IconMaximize size={'small'} />
{/if}
</button>
<div class="hulyHeader-divider" />
<div class="hulyHeader-titleGroup">
<slot />
</div>
{#if $$slots.actions}
<div class="hulyHeader-buttonsGroup">
<slot name="actions" />
</div>
{/if}
</div>
<style lang="scss">
.hulyHeader-container {
display: flex;
align-items: center;
padding: var(--spacing-1_5) var(--spacing-2);
width: 100%;
height: var(--spacing-6_5);
border-bottom: 1px solid var(--theme-divider-color); // var(--global-surface-02-BorderColor);
.hulyHeader-button {
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
padding: 0;
width: 1.5rem;
height: 1.5rem;
color: var(--button-disabled-IconColor);
cursor: pointer;
&:hover {
color: var(--button-subtle-LabelColor);
}
}
.hulyHeader-divider {
flex-shrink: 0;
margin: 0 var(--spacing-2);
width: 1px;
height: var(--spacing-4);
background-color: var(--theme-divider-color); // var(--global-surface-02-BorderColor);
}
.hulyHeader-titleGroup,
.hulyHeader-buttonsGroup {
display: flex;
align-items: center;
min-width: 0;
}
.hulyHeader-titleGroup {
flex-grow: 1;
gap: var(--spacing-0_5);
}
.hulyHeader-buttonsGroup {
gap: var(--spacing-1);
margin-left: var(--spacing-2);
}
}
</style>

View File

@ -0,0 +1,189 @@
<!--
// 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">
export let label: string
export let value: string | undefined = undefined
export let kind: 'default' | 'ghost' = 'default'
export let size: 'small' | 'large' = 'small'
export let disabled: boolean = false
export let error: boolean = false
export let password: boolean = false
export let limit: number = 0
$: labeled = kind === 'default' && size === 'large'
$: placeholder = labeled ? ' ' : label
$: maxlength = limit === 0 ? null : limit
</script>
<label class="editbox-wrapper {kind} {size}" class:error class:disabled>
{#if password}
<input
type="password"
class="font-regular-14"
class:labeled
bind:value
autocomplete="off"
{placeholder}
spellcheck="false"
{disabled}
{maxlength}
on:blur
on:change
on:keyup
on:input
/>
{:else}
<input
type="text"
class="font-regular-14"
class:labeled
bind:value
autocomplete="off"
{placeholder}
spellcheck="false"
{disabled}
{maxlength}
on:blur
on:change
on:keyup
on:input
/>
{/if}
{#if labeled}<div class="font-regular-14 label">{label}</div>{/if}
</label>
<style lang="scss">
.editbox-wrapper {
display: flex;
align-items: center;
min-width: 0;
border-radius: var(--medium-BorderRadius);
&.default {
background-color: var(--input-BackgroundColor);
box-shadow: inset 0 0 0 1px var(--input-BorderColor);
&.small {
padding: var(--spacing-1) var(--spacing-1_5);
height: var(--spacing-4);
}
&.large {
position: relative;
padding: 0 var(--spacing-2);
height: var(--spacing-6_5);
}
}
&.ghost {
&.small {
padding: var(--spacing-1_5) var(--spacing-2);
height: var(--spacing-5);
}
&.large {
padding: var(--spacing-1) var(--spacing-2);
height: var(--spacing-6);
input {
font-weight: 500;
font-size: 1.5rem;
}
}
}
&.error {
box-shadow: inset 0 0 0 1px var(--input-error-BorderColor);
}
&:not(.disabled) {
cursor: text;
&.default {
input::placeholder {
color: var(--input-LabelColor);
}
&:active,
&:focus-within {
background-color: var(--input-BackgroundColor);
outline: 2px solid var(--global-focus-BorderColor);
outline-offset: 2px;
}
&:hover {
background-color: var(--input-hover-BackgroundColor);
}
}
&.ghost input::placeholder {
color: var(--input-PlaceholderColor);
}
&:hover input:not(:focus)::placeholder {
color: var(--input-hover-PlaceholderColor);
}
input:focus::placeholder {
color: var(--input-focus-PlaceholderColor);
}
}
&.disabled {
&,
input {
cursor: not-allowed;
}
input::placeholder {
color: var(--input-PlaceholderColor);
}
&.default {
background-color: transparent;
}
&.ghost {
box-shadow: inset 0 0 0 1px var(--input-BorderColor);
}
}
input {
margin: 0;
padding: 0;
width: 100%;
color: var(--input-TextColor);
caret-color: var(--global-focus-BorderColor);
background-color: transparent;
border: none;
outline: none;
appearance: none;
&.labeled {
height: 100%;
padding-top: var(--spacing-3_5);
padding-bottom: var(--spacing-1_5);
}
}
.label {
position: absolute;
top: 0;
left: 0;
height: 100%;
padding: var(--spacing-2_5) var(--spacing-2);
font-size: 0.875rem;
color: var(--input-LabelColor);
transition:
padding-top 0.2s,
font-size 0.2s;
pointer-events: none;
user-select: none;
}
input:focus + .label,
input:not(:placeholder-shown) + .label {
padding-top: var(--spacing-1_5);
font-size: 0.75rem;
color: var(--input-filled-LabelColor);
}
}
</style>

View File

@ -14,20 +14,25 @@
-->
<script lang="ts">
import type { IntlString } from '@hcengineering/platform'
import type { AnyComponent } from '@hcengineering/ui'
import { Label, Component } from '@hcengineering/ui'
import type { AnyComponent } from '..'
import { Label, Component } from '..'
export let label: IntlString
export let categoryName: string
export let selected: boolean = false
export let tools: AnyComponent | undefined = undefined
export let collapsed: boolean = false
export let second: boolean = false
$: id = `navGroup-${categoryName}`
</script>
<div class="hulyAccordionItem-container">
<button class="hulyAccordionItem-header" class:selected on:click={() => (collapsed = !collapsed)}>
<div class="hulyAccordionItem-container" class:collapsed class:second>
<button
class="hulyAccordionItem-header"
class:selected={selected || !collapsed}
on:click={() => (collapsed = !collapsed)}
>
<div class="hulyAccordionItem-header__label font-medium-12">
<Label {label} />
</div>
@ -53,9 +58,13 @@
display: flex;
flex-direction: column;
min-width: 0;
border-top: 1px solid var(--theme-navpanel-divider);
// border-bottom: 1px solid var(--theme-navpanel-divider); // var(--global-surface-01-BorderColor);
&:not(.second) {
border-top: 1px solid var(--theme-navpanel-divider);
}
&.second.collapsed {
border-bottom: 1px solid var(--theme-navpanel-divider); // var(--global-surface-01-BorderColor);
}
.hulyAccordionItem-header {
display: flex;
justify-content: space-between;

View File

@ -1,5 +1,5 @@
<!--
// Copyright © 2021 Anticrm Platform Contributors.
// Copyright © 2021, 2023 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
@ -14,7 +14,7 @@
-->
<script lang="ts">
import type { Asset, IntlString } from '@hcengineering/platform'
import { Icon, Label, IconOpenedArrow } from '@hcengineering/ui'
import { Icon, Label, IconOpenedArrow, Fold } from '..'
export let icon: Asset | undefined = undefined
export let label: IntlString | undefined = undefined
@ -23,20 +23,30 @@
export let count: number | null = null
export let selected: boolean = false
export let isFold: boolean = false
export let isOpen: boolean = false
export let empty: boolean = false
export let level: number = 1
</script>
<button
class="hulyNavItem-container {type} {type === 'type-anchor-link' ? 'font-regular-12' : 'font-regular-14'}"
class:fold={isFold}
class:selected
on:click|stopPropagation
on:contextmenu|preventDefault|stopPropagation
>
<div class="hulyNavItem-icon">
{#if type !== 'type-tag' && icon}
<Icon {icon} size={'small'} />
{:else if type === 'type-tag'}
<div style:background-color={color} class="hulyNavItem-icon__tag" />
{/if}
</div>
{#if isFold}
<Fold {isOpen} {empty} {level} />
{/if}
{#if icon || (type === 'type-tag' && color)}
<div class="hulyNavItem-icon">
{#if type !== 'type-tag' && icon}
<Icon {icon} size={'small'} />
{:else if type === 'type-tag'}
<div style:background-color={color} class="hulyNavItem-icon__tag" />
{/if}
</div>
{/if}
<span class="hulyNavItem-label" style:color={type === 'type-tag' && selected ? color : null}>
{#if label}<Label {label} />{/if}
</span>
@ -78,8 +88,12 @@
}
&.right {
visibility: hidden;
margin-left: 0.5rem;
color: var(--global-accent-IconColor);
}
&:not(.right) {
margin-right: 0.5rem;
}
}
.hulyNavItem-label {
white-space: nowrap;
@ -91,6 +105,7 @@
color: var(--global-primary-TextColor);
}
.hulyNavItem-count {
margin-left: 0.5rem;
color: var(--global-tertiary-TextColor);
}
&:not(.selected):hover {
@ -109,12 +124,15 @@
}
&.type-link {
gap: 0.5rem;
padding: 0 0.625rem;
&.selected {
padding: 0 0.375rem 0 0.625rem;
&:not(.fold) {
padding: 0 0.375rem 0 0.625rem;
}
&.fold {
padding: 0 0.375rem 0 0.25rem;
}
.hulyNavItem-icon {
color: var(--global-accent-TextColor);
}
@ -128,7 +146,6 @@
}
}
&.type-tag {
gap: 0.5rem;
padding: 0 0.625rem;
.hulyNavItem-label {
@ -136,7 +153,6 @@
}
}
&.type-object {
gap: 0.375rem;
padding: 0 0.625rem 0 0.25rem;
.hulyNavItem-icon {
@ -144,6 +160,10 @@
height: 1.5rem;
background-color: var(--global-ui-BackgroundColor);
border-radius: 0.25rem;
&:not(.right) {
margin-right: 0.375rem;
}
}
.hulyNavItem-label {
flex-grow: 1;
@ -153,7 +173,6 @@
}
}
&.type-anchor-link {
gap: 0.5rem;
padding: 0 0.75rem 0 0.625rem;
min-height: 1.75rem;
@ -169,5 +188,12 @@
color: var(--global-primary-TextColor);
}
}
&.fold {
padding-left: 0.25rem;
:global(.hulyFold-container) {
margin-right: 0.375rem;
}
}
}
</style>

View File

@ -0,0 +1,22 @@
<!--
// 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">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="M11.0001 8L6.00008 3L5.29297 3.70711L9.58586 8L5.29297 12.2929L6.00008 13L11.0001 8Z" />
</svg>

View File

@ -0,0 +1,31 @@
<!--
// 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">
import { IconSize } from '../../types'
export let size: IconSize
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path
d="M2 4.5C2 4.22386 2.22386 4 2.5 4H13.5C13.7761 4 14 4.22386 14 4.5C14 4.77614 13.7761 5 13.5 5H2.5C2.22386 5 2 4.77614 2 4.5Z"
/>
<path
d="M2 8C2 7.72386 2.22386 7.5 2.5 7.5H9.5C9.77614 7.5 10 7.72386 10 8C10 8.27614 9.77614 8.5 9.5 8.5H2.5C2.22386 8.5 2 8.27614 2 8Z"
/>
<path
d="M2 11.5C2 11.2239 2.22386 11 2.5 11H11.5C11.7761 11 12 11.2239 12 11.5C12 11.7761 11.7761 12 11.5 12H2.5C2.22386 12 2 11.7761 2 11.5Z"
/>
</svg>

View File

@ -0,0 +1,23 @@
<!--
// 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">
import { IconSize } from '../../types'
export let size: IconSize
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M7 3V2H2V7H3V3.707L12.293 13H9V14H14V9H13V12.293L3.707 3H7Z" />
</svg>

View File

@ -0,0 +1,28 @@
<!--
// 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">
import { IconSize } from '../../types'
export let size: IconSize
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path
d="M14.1464 1.14645C14.3417 0.951184 14.6583 0.951184 14.8535 1.14645C15.0477 1.34063 15.0488 1.6548 14.8567 1.85031L10.707 5.99999H14V6.99999H8.99996V1.99999H9.99996V5.29299L14.1464 1.14645Z"
/>
<path
d="M1.99996 8.99999V9.99999H5.29296L1.14645 14.1464C0.951184 14.3417 0.951184 14.6583 1.14645 14.8535C1.34171 15.0488 1.65829 15.0488 1.85355 14.8535L5.99996 10.707V14H6.99996V8.99999H1.99996Z"
/>
</svg>

View File

@ -3,37 +3,27 @@
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path
d="M13 9.99988C13 10.5522 13.4477 10.9999 14 10.9999C14.5523 10.9999 15 10.5522 15 9.99988C15 9.44759 14.5523 8.99988 14 8.99988C13.4477 8.99988 13 9.44759 13 9.99988Z"
fill="#8B97AD"
/>
<path
d="M13 13.9999C13 14.5522 13.4477 14.9999 14 14.9999C14.5523 14.9999 15 14.5522 15 13.9999C15 13.4476 14.5523 12.9999 14 12.9999C13.4477 12.9999 13 13.4476 13 13.9999Z"
fill="#8B97AD"
/>
<path
d="M14 18.9999C13.4477 18.9999 13 18.5522 13 17.9999C13 17.4476 13.4477 16.9999 14 16.9999C14.5523 16.9999 15 17.4476 15 17.9999C15 18.5522 14.5523 18.9999 14 18.9999Z"
fill="#8B97AD"
/>
<path
d="M13 6C13 6.55228 13.4477 7 14 7C14.5523 7 15 6.55228 15 6C15 5.44772 14.5523 5 14 5C13.4477 5 13 5.44772 13 6Z"
fill="#8B97AD"
d="M9 5.99988C9 6.55216 9.44772 6.99988 10 6.99988C10.5523 6.99988 11 6.55216 11 5.99988C11 5.44759 10.5523 4.99988 10 4.99988C9.44772 4.99988 9 5.44759 9 5.99988Z"
/>
<path
d="M9 9.99988C9 10.5522 9.44772 10.9999 10 10.9999C10.5523 10.9999 11 10.5522 11 9.99988C11 9.44759 10.5523 8.99988 10 8.99988C9.44772 8.99988 9 9.44759 9 9.99988Z"
fill="#8B97AD"
/>
<path
d="M9 13.9999C9 14.5522 9.44772 14.9999 10 14.9999C10.5523 14.9999 11 14.5522 11 13.9999C11 13.4476 10.5523 12.9999 10 12.9999C9.44772 12.9999 9 13.4476 9 13.9999Z"
fill="#8B97AD"
d="M10 14.9999C9.44772 14.9999 9 14.5522 9 13.9999C9 13.4476 9.44772 12.9999 10 12.9999C10.5523 12.9999 11 13.4476 11 13.9999C11 14.5522 10.5523 14.9999 10 14.9999Z"
/>
<path
d="M10 18.9999C9.44772 18.9999 9 18.5522 9 17.9999C9 17.4476 9.44772 16.9999 10 16.9999C10.5523 16.9999 11 17.4476 11 17.9999C11 18.5522 10.5523 18.9999 10 18.9999Z"
fill="#8B97AD"
d="M9 2C9 2.55228 9.44772 3 10 3C10.5523 3 11 2.55228 11 2C11 1.44772 10.5523 1 10 1C9.44772 1 9 1.44772 9 2Z"
/>
<path
d="M9 6C9 6.55228 9.44772 7 10 7C10.5523 7 11 6.55228 11 6C11 5.44772 10.5523 5 10 5C9.44772 5 9 5.44772 9 6Z"
fill="#8B97AD"
d="M5 5.99988C5 6.55216 5.44772 6.99988 6 6.99988C6.55229 6.99988 7 6.55216 7 5.99988C7 5.44759 6.55229 4.99988 6 4.99988C5.44772 4.99988 5 5.44759 5 5.99988Z"
/>
<path
d="M5 9.99988C5 10.5522 5.44772 10.9999 6 10.9999C6.55228 10.9999 7 10.5522 7 9.99988C7 9.44759 6.55229 8.99988 6 8.99988C5.44772 8.99988 5 9.44759 5 9.99988Z"
/>
<path
d="M6 14.9999C5.44772 14.9999 5 14.5522 5 13.9999C5 13.4476 5.44772 12.9999 6 12.9999C6.55228 12.9999 7 13.4476 7 13.9999C7 14.5522 6.55228 14.9999 6 14.9999Z"
/>
<path d="M5 2C5 2.55228 5.44772 3 6 3C6.55229 3 7 2.55228 7 2C7 1.44772 6.55229 1 6 1C5.44772 1 5 1.44772 5 2Z" />
</svg>

View File

@ -1,6 +1,5 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
// 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

View File

@ -211,7 +211,7 @@
font-size: 12px;
line-height: 150%;
background-color: var(--theme-statusbar-color);
border-bottom: 1px solid var(--theme-navpanel-divider);
// border-bottom: 1px solid var(--theme-navpanel-divider);
.history-box {
-webkit-app-region: no-drag;

View File

@ -123,6 +123,14 @@ export { default as Chevron } from './components/Chevron.svelte'
export { default as Timeline } from './components/Timeline.svelte'
export { default as TimeShiftPresenter } from './components/TimeShiftPresenter.svelte'
export { default as Separator } from './components/Separator.svelte'
export { default as Fold } from './components/Fold.svelte'
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 ModernEditbox } from './components/ModernEditbox.svelte'
export { default as NavItem } from './components/NavItem.svelte'
export { default as NavGroup } from './components/NavGroup.svelte'
export { default as IconAdd } from './components/icons/Add.svelte'
export { default as IconCircleAdd } from './components/icons/CircleAdd.svelte'
@ -183,6 +191,11 @@ export { default as IconObjects } from './components/icons/Objects.svelte'
export { default as IconUndo } from './components/icons/Undo.svelte'
export { default as IconRedo } from './components/icons/Redo.svelte'
export { default as IconOpenedArrow } from './components/icons/OpenedArrow.svelte'
export { default as IconMaximize } from './components/icons/Maximize.svelte'
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 PanelInstance } from './components/PanelInstance.svelte'
export { default as Panel } from './components/Panel.svelte'

View File

@ -1,61 +0,0 @@
<!--
// 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">
import type { Asset, IntlString } from '@hcengineering/platform'
import { Icon, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let icon: Asset | undefined = undefined
export let label: IntlString | undefined = undefined
export let selected: boolean = false
export let expandable = false
const dispatch = createEventDispatcher()
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="antiNav-element"
class:selected
class:expandable
on:click|stopPropagation={() => {
dispatch('click')
}}
>
<div class="an-element__icon">
{#if icon}
<Icon {icon} size={'small'} />
{/if}
</div>
<span class="an-element__label">
{#if label}<Label {label} />{/if}
</span>
</div>
<style lang="scss">
.expandable {
position: relative;
&::after {
content: '▶';
position: absolute;
top: 50%;
right: 0.5rem;
font-size: 0.375rem;
color: var(--dark-color);
transform: translateY(-50%);
}
}
</style>

View File

@ -106,37 +106,34 @@
}
</script>
<div class="flex-grow vScroll w-full">
<div class="container">
<Grid {column} columnGap={5} rowGap={1.5}>
{#each types as type}
<div class="flex">
{#if type.generated}
<Label label={getLabel(type)} />:
{/if}
<Label label={type.label} />
</div>
{#each providers as provider (provider._id)}
{#if type.providers[provider._id] !== undefined}
<div class="toggle">
<ToggleWithLabel
label={provider.label}
on={getStatus(settings, type._id, provider._id)}
on:change={createHandler(type._id, provider._id)}
/>
</div>
{:else}
<div />
{/if}
{/each}
<div class="container">
<Grid {column} columnGap={5} rowGap={1.5}>
{#each types as type}
<div class="flex">
{#if type.generated}
<Label label={getLabel(type)} />:
{/if}
<Label label={type.label} />
</div>
{#each providers as provider (provider._id)}
{#if type.providers[provider._id] !== undefined}
<div class="toggle">
<ToggleWithLabel
label={provider.label}
on={getStatus(settings, type._id, provider._id)}
on:change={createHandler(type._id, provider._id)}
/>
</div>
{:else}
<div />
{/if}
{/each}
</Grid>
</div>
{/each}
</Grid>
</div>
<style lang="scss">
.container {
padding: 3rem;
width: fit-content;
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher, onDestroy } from 'svelte'
import { Ref } from '@hcengineering/core'
import type {
NotificationGroup,
@ -22,12 +23,26 @@
} from '@hcengineering/notification'
import { getResource } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Location, Scroller, getCurrentResolvedLocation, navigate, resolvedLocationStore } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import {
Location,
Scroller,
getCurrentResolvedLocation,
navigate,
resolvedLocationStore,
Header,
Breadcrumb,
defineSeparators,
settingsSeparators,
Separator,
NavItem
} from '@hcengineering/ui'
import notification from '../plugin'
import GroupElement from './GroupElement.svelte'
import NotificationGroupSetting from './NotificationGroupSetting.svelte'
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const client = getClient()
let groups: NotificationGroup[] = []
let preferencesGroups: NotificationPreferencesGroup[] = []
@ -73,14 +88,23 @@
})(loc)
})
)
defineSeparators('notificationSettings', settingsSeparators)
</script>
<div class="flex">
<div class="antiPanel-element ml-4 mt-2">
<div class="antiPanel-wrap__content">
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb
icon={notification.icon.Notifications}
label={notification.string.Notifications}
size={'large'}
isCurrent
/>
</Header>
<div class="hulyComponent-content__container columns">
<div class="hulyComponent-content__column navigation py-2">
<Scroller shrink>
{#each preferencesGroups as preferenceGroup}
<GroupElement
<NavItem
icon={preferenceGroup.icon}
label={preferenceGroup.label}
selected={preferenceGroup === currentPreferenceGroup}
@ -94,10 +118,10 @@
/>
{/each}
{#if preferencesGroups.length > 0 && groups.length > 0}
<div class="antiNav-divider short line" />
<div class="antiNav-divider line" />
{/if}
{#each groups as gr}
<GroupElement
<NavItem
icon={gr.icon}
label={gr.label}
selected={gr._id === group}
@ -114,15 +138,20 @@
<div class="antiNav-space" />
</Scroller>
</div>
</div>
<div class="antiPanel-component filled">
{#if group}
<NotificationGroupSetting {group} {settings} />
{/if}
{#if currentPreferenceGroup}
{#await getResource(currentPreferenceGroup.presenter) then presenter}
<svelte:component this={presenter} />
{/await}
{/if}
<Separator name={'notificationSettings'} index={0} color={'var(--theme-divider-color)'} />
<div class="hulyComponent-content__column content">
<div class="hulyComponent-content">
<Scroller>
{#if group}
<NotificationGroupSetting {group} {settings} />
{/if}
{#if currentPreferenceGroup}
{#await getResource(currentPreferenceGroup.presenter) then presenter}
<svelte:component this={presenter} />
{/await}
{/if}
</Scroller>
</div>
</div>
</div>
</div>

View File

@ -31,6 +31,8 @@
"IntegrationDisabledDescr": "Integration disabled",
"IntegrationWith": "Integration with ",
"ClassSetting": "Class setting",
"ClassSettingHint": "A set or category of things having some property or attribute in common from others by kind, type, or quality.",
"ClassProperties": "Class properties",
"Classes": "Classes",
"Attributes": "Attributes",
"DeleteAttribute": "Delete attribute",

View File

@ -31,6 +31,8 @@
"IntegrationDisabledDescr": "Интеграция отключена",
"IntegrationWith": "Интеграция с ",
"ClassSetting": "Настройки класса",
"ClassSettingHint": "Набор или категория вещей, обладающих каким-либо свойством или атрибутом, отличающимся от других по виду, типу или качеству.",
"ClassProperties": "Свойства класса",
"Classes": "Классы",
"Attributes": "Атрибуты",
"DeleteAttribute": "Удалить атрибут",

View File

@ -32,7 +32,7 @@
Action,
ActionIcon,
AnySvelteComponent,
CircleButton,
ButtonIcon,
Icon,
IconAdd,
IconDelete,
@ -41,7 +41,8 @@
Label,
Menu,
getEventPositionElement,
showPopup
showPopup,
IconSettings
} from '@hcengineering/ui'
import { getContextActions } from '@hcengineering/view-resources'
import settings from '../plugin'
@ -53,7 +54,6 @@
export let ofClass: Ref<Class<Doc>> | undefined = undefined
export let useOfClassAttributes = true
export let showTitle = true
export let showCreate = true
export let attributeMapper:
| {
@ -69,6 +69,7 @@
const classQuery = createQuery()
let clazz: Class<Doc> | undefined
let hovered: number | null = null
$: classQuery.query(core.class.Class, { _id: _class }, (res) => {
clazz = res.shift()
@ -120,7 +121,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[] = [
@ -152,7 +154,9 @@
}
}))
)
showPopup(Menu, { actions }, getEventPositionElement(ev))
showPopup(Menu, { actions }, getEventPositionElement(ev), () => {
hovered = null
})
}
function getAttrType (type: Type<any>): IntlString | undefined {
@ -179,104 +183,176 @@
</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}
{#if clazz}
<div class="flex-row-center flex-no-shrink mb-6">
<div class="hulyInput-body">
<Label label={clazz.label} />
</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>
</div>
{/if}
{/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 class="hulyTableAttr-container">
<div class="hulyTableAttr-header font-medium-12">
<IconSettings size={'small'} />
<span><Label label={settings.string.ClassProperties} /></span>
<ButtonIcon kind={'primary'} icon={IconAdd} size={'small'} on:click={createAttribute} />
</div>
{/if}
{#each attributes as attr, i}
{@const attrType = getAttrType(attr.type)}
<tr
class="antiTable-body__row"
on:contextmenu={(ev) => {
ev.preventDefault()
void showMenu(ev, attr)
}}
>
<td>
{#if i === 0 && clazz?.label !== undefined}
<div class="trans-title">
<Label label={clazz.label} />
</div>
{/if}
</td>
<td>
<div class="antiTable-cells__firstCell whitespace-nowrap flex-row-center">
<!-- svelte-ignore a11y-click-events-have-key-events -->
{#if attributes.length}
<div class="hulyTableAttr-content">
{#each attributes as attr, i}
{@const attrType = getAttrType(attr.type)}
<!-- 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
class="hulyTableAttr-content__row"
class:hovered={hovered === i}
on:contextmenu={(ev) => {
ev.preventDefault()
void showMenu(ev, 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={(ev) => showMenu(ev, attr, i)}>
<IconMoreV2 size={'small'} />
</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 attr.isCustom}
<div class="hulyTableAttr-content__row-chip font-medium-12">
<Label label={settings.string.Custom} />
</div>
{/if}
{/await}
{/if}
</td>
{#if attributeMapper}
<td>
<svelte:component this={attributeMapper.component} {...attributeMapper.props} attribute={attr} />
</td>
{/if}
</tr>
{/each}
{#if attributes.length === 0}
<tr class="antiTable-body__row">
<td>
<div class="trans-title">
{#if clazz}
<Label label={clazz.label} />
{/if}
</div>
</td>
<td class="select-text whitespace-nowrap"> </td>
<td> </td>
{#if attributeMapper}
<td> </td>
{/if}
</tr>
{/if}
{#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>
{/each}
</div>
{/if}
</div>
<style lang="scss">
.hulyInput-body {
flex-grow: 1;
flex-shrink: 0;
padding: var(--spacing-1) var(--spacing-2);
font-weight: 500;
font-size: 1.5rem;
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;
&.hovered,
&:hover {
background-color: var(--theme-table-header-color); // var(--global-surface-03-hover-BackgroundColor);
}
&-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);
}
&-chip {
padding: var(--spacing-0_25) var(--spacing-0_5);
text-transform: uppercase;
color: var(--global-tertiary-TextColor);
background-color: var(--global-ui-BackgroundColor);
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);
}
}
}
}
</style>

View File

@ -0,0 +1,282 @@
<!--
// 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,
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 {
Action,
ActionIcon,
AnySvelteComponent,
CircleButton,
Icon,
IconAdd,
IconDelete,
IconEdit,
IconMoreV2,
Label,
Menu,
getEventPositionElement,
showPopup
} from '@hcengineering/ui'
import { getContextActions } from '@hcengineering/view-resources'
import settings from '../plugin'
import CreateAttribute from './CreateAttribute.svelte'
import EditAttribute from './EditAttribute.svelte'
import EditClassLabel from './EditClassLabel.svelte'
export let _class: Ref<Class<Doc>>
export let ofClass: Ref<Class<Doc>> | undefined = undefined
export let useOfClassAttributes = true
export let showTitle = true
export let showCreate = true
export let attributeMapper:
| {
component: AnySvelteComponent
label: IntlString
props: Record<string, any>
}
| undefined = undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const classQuery = createQuery()
let clazz: Class<Doc> | undefined
$: classQuery.query(core.class.Class, { _id: _class }, (res) => {
clazz = res.shift()
})
$: attributes = getCustomAttributes(_class)
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()
)
return attributes
}
const attrQuery = createQuery()
$: attrQuery.query(core.class.Attribute, { attributeOf: _class }, () => {
attributes = getCustomAttributes(_class)
})
function update (): void {
attributes = getCustomAttributes(_class)
}
export function createAttribute (): void {
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): Promise<void> {
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exists: true } })) !== undefined
const actions: Action[] = [
{
label: presentation.string.Edit,
icon: IconEdit,
action: async () => {
await editAttribute(attribute, exist)
}
}
]
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))
}
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))
}
</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)
}}
>
<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>
{/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

@ -15,15 +15,15 @@
<script lang="ts">
import { Class, ClassifierKind, Doc, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Icon, IconAdd, getEventPositionElement, showPopup } from '@hcengineering/ui'
import { getEventPositionElement, showPopup, NavItem } from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import ObjectPresenter from '@hcengineering/view-resources/src/components/ObjectPresenter.svelte'
import { createEventDispatcher } from 'svelte'
import settings from '../plugin'
export let classes: Ref<Class<Doc>>[] = ['contact:class:Contact' as Ref<Class<Doc>>]
export let _class: Ref<Class<Doc>> | undefined
export let ofClass: Ref<Class<Doc>> | undefined
export let level: number = 1
const client = getClient()
const dispatch = createEventDispatcher()
@ -58,35 +58,20 @@
{#each classes as cl}
{@const clazz = client.getHierarchy().getClass(cl)}
{@const desc = getDescendants(cl)}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="ac-column__list-item"
class:ac-column__list-selected={cl === _class}
<NavItem
label={clazz.label}
isFold
empty
{level}
selected={cl === _class}
on:click={() => {
dispatch('select', cl)
}}
on:contextmenu|preventDefault|stopPropagation={(evt) => {
on:contextmenu={(evt) => {
showContextMenu(evt, clazz)
}}
>
<div class="flex-row-center">
{#if clazz.icon}
<div class="mr-1 flex">
<Icon icon={clazz.icon} size={'medium'} />
{#if clazz.kind === ClassifierKind.MIXIN && client.getHierarchy().hasMixin(clazz, settings.mixin.UserMixin)}
<Icon icon={IconAdd} size={'x-small'} fill={'var(--theme-dark-color)'} />
{/if}
</div>
{/if}
<span class="overflow-label caption-color">
<ObjectPresenter _class={clazz._class} objectId={clazz._id} value={clazz} />
</span>
</div>
</div>
/>
{#if desc.length}
<div class="ml-8 mt-3 mb-3">
<svelte:self classes={desc} {_class} on:select />
</div>
<svelte:self classes={desc} {_class} level={level + 1} on:select />
{/if}
{/each}

View File

@ -13,10 +13,25 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import core, { Class, Doc, Obj, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { AnySvelteComponent, Icon, Label, getLocation, navigate } from '@hcengineering/ui'
import {
AnySvelteComponent,
Scroller,
ButtonIcon,
IconDescription,
Label,
getLocation,
navigate,
Header,
Breadcrumb,
defineSeparators,
settingsSeparators,
Separator,
NavGroup
} from '@hcengineering/ui'
import setting from '../plugin'
import { filterDescendants } from '../utils'
import ClassAttributes from './ClassAttributes.svelte'
@ -32,6 +47,9 @@
| undefined = undefined
export let withoutHeader = false
export let useOfClassAttributes = true
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const loc = getLocation()
const client = getClient()
@ -80,36 +98,47 @@
$: if (ofClass !== undefined && _class !== undefined && !client.getHierarchy().isDerived(_class, ofClass)) {
_class = ofClass
}
defineSeparators('workspaceSettings', settingsSeparators)
</script>
<div class="antiComponent">
<div class="hulyComponent">
{#if !withoutHeader}
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={setting.icon.Clazz} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.ClassSetting} /></div>
</div>
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb icon={setting.icon.Clazz} label={setting.string.ClassSetting} size={'large'} isCurrent />
</Header>
{/if}
<div class="ac-body columns hScroll">
<div class="ac-column">
<div class="overflow-y-auto">
<ClassHierarchy
{classes}
{_class}
{ofClass}
on:select={(e) => {
_class = e.detail
}}
/>
<div class="hulyComponent-content__container columns">
<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 class="hulyComponent-content__navHeader-hint paragraph-regular-14">
<Label label={setting.string.ClassSettingHint} />
</div>
</div>
<Scroller>
<NavGroup label={setting.string.Classes} selected={_class !== undefined} categoryName={'classes'} second>
<ClassHierarchy
{classes}
{_class}
{ofClass}
on:select={(e) => {
_class = e.detail
}}
/>
</NavGroup>
</Scroller>
</div>
<div class="ac-column max">
{#if _class !== undefined}
<table class="antiTable">
<tbody>
<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} />
</tbody>
</table>
{/if}
</Scroller>
{/if}
</div>
</div>
</div>
</div>

View File

@ -13,11 +13,16 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { PluginConfiguration } from '@hcengineering/core'
import { configurationStore, getClient } from '@hcengineering/presentation'
import { Button, Icon, IconInfo, Label, Scroller } from '@hcengineering/ui'
import { Button, Icon, IconInfo, Label, Header, Breadcrumb } from '@hcengineering/ui'
import setting from '../plugin'
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const client = getClient()
async function change (config: PluginConfiguration, value: boolean): Promise<void> {
@ -27,13 +32,12 @@
}
</script>
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={setting.icon.Setting} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.Configuration} /></div>
</div>
<Scroller>
<div class="flex-row-center flex-wrap p-1 gap-around-4">
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<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}>
@ -66,7 +70,7 @@
{/if}
{/each}
</div>
</Scroller>
</div>
</div>
<style lang="scss">

View File

@ -13,26 +13,36 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import core, { Enum } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import {
CircleButton,
EditBox,
Icon,
eventToHTMLElement,
IconAdd,
IconMoreH,
Label,
showPopup
showPopup,
Header,
Breadcrumb,
defineSeparators,
settingsSeparators,
Separator
} from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import setting from '../plugin'
import EnumValues from './EnumValues.svelte'
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const query = createQuery()
let enums: Enum[] = []
let selected: Enum | undefined
let hovered: number | null = null
const client = getClient()
query.query(core.class.Enum, {}, (res) => {
@ -51,25 +61,26 @@
name: value.name
})
}
defineSeparators('workspaceSettings', settingsSeparators)
</script>
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={setting.icon.Enums} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.Enums} /></div>
</div>
<div class="ac-body columns hScroll">
<div class="ac-column">
<div class="flex-between trans-title mb-3">
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb icon={setting.icon.Enums} label={setting.string.Enums} size={'large'} isCurrent />
</Header>
<div class="hulyComponent-content__container columns">
<div class="hulyComponent-content__column">
<div class="flex-between trans-title m-3">
<Label label={setting.string.Enums} />
<CircleButton icon={IconAdd} size="medium" on:click={create} />
</div>
<div class="overflow-y-auto">
{#each enums as value}
{#each enums as value, i}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="ac-column__list-item"
class="enum__list-item"
class:hovered={hovered === i}
class:selected={selected === value}
on:click={() => {
selected = value
@ -79,7 +90,10 @@
<div
class="hover-trans"
on:click|stopPropagation={(ev) => {
showPopup(ContextMenu, { object: value }, eventToHTMLElement(ev), () => {})
hovered = i
showPopup(ContextMenu, { object: value }, eventToHTMLElement(ev), () => {
hovered = null
})
}}
>
<IconMoreH size={'medium'} />
@ -88,10 +102,37 @@
{/each}
</div>
</div>
<div class="ac-column max">
{#if selected !== undefined}
<EnumValues value={selected} />
{/if}
<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>
</div>
</div>
</div>
<style lang="scss">
.enum__list-item {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 2.5rem;
margin: 0 0.75rem;
padding: 0 1.25rem;
border: 1px solid transparent;
border-radius: 12px;
cursor: pointer;
&.hovered,
&:hover {
background-color: var(--theme-button-hovered);
}
&.selected {
background-color: var(--theme-button-default);
border-color: var(--theme-button-border);
cursor: auto;
}
}
</style>

View File

@ -13,13 +13,18 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Ref, getCurrentAccount } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import type { Integration, IntegrationType } from '@hcengineering/setting'
import setting from '@hcengineering/setting'
import { Icon, Label } from '@hcengineering/ui'
import { Header, Breadcrumb } from '@hcengineering/ui'
import PluginCard from './PluginCard.svelte'
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const accountId = getCurrentAccount()._id
const typeQuery = createQuery()
const integrationQuery = createQuery()
@ -39,11 +44,10 @@
}
</script>
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={setting.icon.Integrations} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.Integrations} /></div>
</div>
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb icon={setting.icon.Integrations} label={setting.string.Integrations} size={'large'} isCurrent />
</Header>
<div class="ac-body__cards-container">
{#each integrationTypes as integrationType (integrationType._id)}
{#if integrationType.allowMultiple}

View File

@ -13,10 +13,15 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import login from '@hcengineering/login'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import setting, { InviteSettings } from '@hcengineering/setting'
import { Button, EditBox, MiniToggle } from '@hcengineering/ui'
import { Button, EditBox, MiniToggle, Header, Breadcrumb } from '@hcengineering/ui'
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const client = getClient()
let expTime: number = 48
@ -55,23 +60,28 @@
}
</script>
<div class="form">
<div class="mt-2">
<EditBox label={login.string.LinkValidHours} format={'number'} bind:value={expTime} />
</div>
<div class="mt-2">
<EditBox label={login.string.EmailMask} bind:value={mask} />
</div>
<div class="mt-2">
<MiniToggle bind:on={noLimit} label={login.string.NoLimit} on:change={() => noLimit && (limit = -1)} />
</div>
{#if !noLimit}
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb icon={setting.icon.InviteSettings} label={setting.string.InviteSettings} size={'large'} isCurrent />
</Header>
<div class="form">
<div class="mt-2">
<EditBox label={login.string.InviteLimit} format={'number'} bind:value={limit} />
<EditBox label={login.string.LinkValidHours} format={'number'} bind:value={expTime} />
</div>
<div class="mt-2">
<EditBox label={login.string.EmailMask} bind:value={mask} />
</div>
<div class="mt-2">
<MiniToggle bind:on={noLimit} label={login.string.NoLimit} on:change={() => noLimit && (limit = -1)} />
</div>
{#if !noLimit}
<div class="mt-2">
<EditBox label={login.string.InviteLimit} format={'number'} bind:value={limit} />
</div>
{/if}
<div class="mt-2">
<Button label={presentation.string.Save} size={'medium'} kind={'primary'} on:click={() => setInviteSettings()} />
</div>
{/if}
<div class="mt-2">
<Button label={presentation.string.Save} size={'medium'} kind={'primary'} on:click={() => setInviteSettings()} />
</div>
</div>

View File

@ -13,13 +13,18 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import contact, { PersonAccount } from '@hcengineering/contact'
import { EmployeePresenter, personByIdStore } from '@hcengineering/contact-resources'
import { AccountRole, SortingOrder, getCurrentAccount } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Icon, Label } from '@hcengineering/ui'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Header, Breadcrumb } from '@hcengineering/ui'
import setting from '../plugin'
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const client = getClient()
const query = createQuery()
@ -54,14 +59,13 @@
let search = ''
</script>
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={setting.icon.Password} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.Owners} /></div>
<EditBox kind={'search-style'} focusIndex={1} bind:value={search} />
</div>
<div class="ac-body columns">
<div class="ac-column max">
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb icon={setting.icon.Owners} label={setting.string.Owners} size={'large'} isCurrent />
<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)}

View File

@ -13,14 +13,19 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import setting from '@hcengineering/setting'
import presentation from '@hcengineering/presentation'
import { Button, EditBox, Icon, Label } from '@hcengineering/ui'
import { Button, EditBox, Icon, Label, Header, Breadcrumb } from '@hcengineering/ui'
import login from '@hcengineering/login'
import Error from './icons/Error.svelte'
import plugin from '../plugin'
import { getResource } from '@hcengineering/platform'
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
let oldPassword: string = ''
let password: string = ''
let password2: string = ''
@ -54,11 +59,10 @@
$: updateSaved(oldPassword, password, password2)
</script>
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={setting.icon.Password} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.ChangePassword} /></div>
</div>
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb icon={setting.icon.Password} label={setting.string.ChangePassword} size={'large'} isCurrent />
</Header>
<div class="flex-row-stretch flex-grow p-10">
<div class="flex-grow flex-col">
{#if error}

View File

@ -13,16 +13,20 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher, onDestroy } from 'svelte'
import contact, { Employee, PersonAccount, combineName, getFirstName, getLastName } from '@hcengineering/contact'
import { ChannelsEditor, EditableAvatar, employeeByIdStore } from '@hcengineering/contact-resources'
import { Ref, getCurrentAccount } from '@hcengineering/core'
import login from '@hcengineering/login'
import { getResource } from '@hcengineering/platform'
import { AttributeEditor, getClient, MessageBox } from '@hcengineering/presentation'
import { Button, createFocusManager, EditBox, FocusHandler, Icon, Label, showPopup } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import { Button, createFocusManager, EditBox, FocusHandler, showPopup, Header, Breadcrumb } from '@hcengineering/ui'
import setting from '../plugin'
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const client = getClient()
let avatarEditor: EditableAvatar
@ -84,11 +88,10 @@
<FocusHandler {manager} />
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={setting.icon.AccountSettings} size={'medium'} /></div>
<div class="ac-header__title"><Label label={setting.string.AccountSettings} /></div>
</div>
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb icon={setting.icon.AccountSettings} label={setting.string.AccountSettings} size={'large'} isCurrent />
</Header>
<div class="ac-body p-10">
{#if employee}
<div class="flex flex-grow w-full">

View File

@ -31,12 +31,12 @@
resolvedLocationStore,
setMetadataLocalStorage,
showPopup,
Label
Label,
NavItem,
NavGroup
} from '@hcengineering/ui'
import { NavFooter } from '@hcengineering/workbench-resources'
import { onDestroy } from 'svelte'
import NavItem from './NavItem.svelte'
import NavGroup from './NavGroup.svelte'
export let visibleNav: boolean = true
export let navFloat: boolean = false
@ -105,9 +105,12 @@
defineSeparators('setting', settingsSeparators)
</script>
<div class="flex h-full clear-mins">
<div class="hulyPanels-container">
{#if visibleNav}
<div class="antiPanel-navigator {appsDirection === 'horizontal' ? 'portrait' : 'landscape'}">
<div
class="antiPanel-navigator {appsDirection === 'horizontal' ? 'portrait' : 'landscape'} border-left"
class:border-right={category?.component === undefined}
>
<div class="antiPanel-wrap__content hulyNavPanel-container">
<div class="hulyNavPanel-header">
<Label label={setting.string.Settings} />
@ -157,29 +160,40 @@
<NavItem icon={setting.icon.Signout} label={setting.string.Signout} on:click={signOut} />
</NavFooter>
</div>
<Separator
name={'setting'}
float={navFloat ? 'navigator' : true}
index={0}
color={'var(--theme-navpanel-border)'}
/>
<Separator name={'setting'} float={navFloat ? 'navigator' : true} index={0} color={'transparent'} />
</div>
<Separator name={'setting'} float={navFloat} index={0} color={'var(--theme-navpanel-border)'} />
<Separator name={'setting'} float={navFloat} index={0} color={'transparent'} />
{/if}
<div class="antiPanel-component filled">
<div class="antiPanel-component filledNav">
{#if category}
<Component
is={category.component}
props={{
kind: 'content',
visibleNav
}}
on:change={(event) => (visibleNav = event.detail)}
/>
{/if}
</div>
</div>
<style lang="scss">
.hulyPanels-container {
display: flex;
height: 100%;
min-width: 0;
min-height: 0;
background-color: var(--theme-navpanel-color); // var(--global-surface-01-BackgroundColor);
// .antiPanel-navigator {
// background-color: transparent;
// }
.antiPanel-component {
border-radius: var(--small-focus-BorderRadius);
}
}
.hulyNavPanel-container :global(.hulyNavItem-container),
.hulyNavPanel-container :global(.hulyTaskNavLink-container) {
margin: 0 0.75rem;

View File

@ -17,12 +17,19 @@
import { AccountRole, getCurrentAccount } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import setting, { SettingsCategory } from '@hcengineering/setting'
import { Component, Location, getCurrentResolvedLocation, navigate, resolvedLocationStore } from '@hcengineering/ui'
import {
Component,
Location,
getCurrentResolvedLocation,
navigate,
resolvedLocationStore,
NavItem
} from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import NavItem from './NavItem.svelte'
export let kind: 'navigation' | undefined
export let kind: 'navigation' | 'content' | undefined
export let categoryName: string
export let visibleNav: boolean = true
let category: SettingsCategory | undefined
let categoryId: string = ''
@ -81,6 +88,8 @@
<Component is={category.extraComponents?.navigation} props={{ kind: 'navigation', categoryName: categoryId }} />
{/if}
{/each}
{:else if kind === 'content' && !category}
<div class="hulyComponent" />
{:else if category}
<Component is={category.component} props={{ kind: 'content' }} />
<Component is={category.component} props={{ kind: 'content', visibleNav }} on:change />
{/if}

View File

@ -47,8 +47,9 @@ import setting from './plugin'
import IntegrationPanel from './components/IntegrationPanel.svelte'
import { getOwnerFirstName, getOwnerLastName, getOwnerPosition, getValue, filterDescendants } from './utils'
import ClassAttributes from './components/ClassAttributes.svelte'
import ClassAttributesList from './components/ClassAttributesList.svelte'
export { ClassSetting, filterDescendants, ClassAttributes }
export { ClassSetting, filterDescendants, ClassAttributes, ClassAttributesList }
async function DeleteMixin (object: Mixin<Class<Doc>>): Promise<void> {
const docs = await getClient().findAll(object._id, {}, { limit: 1 })

View File

@ -66,7 +66,6 @@ export default mergeIds(settingId, setting, {
ShowAttribute: '' as IntlString,
Visibility: '' as IntlString,
Hidden: '' as IntlString,
InviteSettings: '' as IntlString,
DefaultValue: '' as IntlString,
SelectAValue: '' as IntlString,
DateOnly: '' as IntlString,
@ -77,6 +76,8 @@ export default mergeIds(settingId, setting, {
ConfigurationDisabled: '' as IntlString,
ConfigDisable: '' as IntlString,
ConfigEnable: '' as IntlString,
ConfigBeta: '' as IntlString
ConfigBeta: '' as IntlString,
ClassSettingHint: '' as IntlString,
ClassProperties: '' as IntlString
}
})

View File

@ -168,7 +168,8 @@ export default plugin(settingId, {
ClassSetting: '' as IntlString,
Classes: '' as IntlString,
Owners: '' as IntlString,
Configure: '' as IntlString
Configure: '' as IntlString,
InviteSettings: '' as IntlString
},
icon: {
AccountSettings: '' as Asset,

View File

@ -22,6 +22,8 @@
import { typeStore } from '../../'
import ProjectEditor from './ProjectEditor.svelte'
export let visibleNav: boolean = true
let type: WithLookup<ProjectType> | undefined
let typeId: Ref<ProjectType> | undefined
@ -36,8 +38,8 @@
$: type = typeId !== undefined ? $typeStore.get(typeId) : undefined
</script>
<div class="p-1 w-full h-full">
<div class="hulyComponent">
{#if type !== undefined}
<ProjectEditor {type} descriptor={type.$lookup?.descriptor} />
<ProjectEditor {type} descriptor={type.$lookup?.descriptor} {visibleNav} on:change />
{/if}
</div>

View File

@ -14,6 +14,7 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher, onDestroy } from 'svelte'
import { ComponentExtensions, createQuery, getClient } from '@hcengineering/presentation'
import task, { Project, ProjectType, ProjectTypeDescriptor, Task, TaskType } from '@hcengineering/task'
@ -21,6 +22,7 @@
import { getEmbeddedLabel } from '@hcengineering/platform'
import {
Button,
ButtonIcon,
Component,
EditBox,
Icon,
@ -31,15 +33,15 @@
IconMoreV,
Label,
Location,
Scroller,
eventToHTMLElement,
getCurrentResolvedLocation,
navigate,
resolvedLocationStore,
showPopup
showPopup,
Header,
Breadcrumbs
} from '@hcengineering/ui'
import { ContextMenu } from '@hcengineering/view-resources'
import { onDestroy } from 'svelte'
import plugin from '../../plugin'
import CreateTaskType from '../taskTypes/CreateTaskType.svelte'
import TaskTypeEditor from '../taskTypes/TaskTypeEditor.svelte'
@ -49,6 +51,9 @@
export let type: ProjectType
export let descriptor: ProjectTypeDescriptor | undefined
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const client = getClient()
const query = createQuery()
@ -141,173 +146,160 @@
selectedTaskTypeId = id as Ref<TaskType>
navigate(loc)
}
$: items =
selectedTaskType !== undefined
? [{ label: plugin.string.ProjectType }, { title: selectedTaskType.name }]
: [{ label: plugin.string.ProjectType }]
</script>
<div class="h-full flex-col w-full">
{#if type !== undefined && descriptor !== undefined}
<div class="p-2 bottom-divider flex-row-center">
<div class="button-group flex-row-center right-divider pr-2">
<Button
icon={IconDelete}
kind={'regular'}
on:click={(ev) => {
// Ask for delete
}}
/>
<Button
icon={IconCopy}
kind={'regular'}
on:click={(ev) => {
// Do copy of type
}}
/>
<Button
icon={IconMoreV}
kind={'regular'}
on:click={(ev) => {
showPopup(ContextMenu, { object: type }, eventToHTMLElement(ev), () => {})
}}
/>
</div>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="fs-title ml-2 flex-row-center">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="hover-trans"
on:click={() => {
selectTaskType(undefined)
}}
>
<Label label={plugin.string.ProjectType} />
</div>
{#if selectedTaskType !== undefined}
<span class="p-1">/</span>
{selectedTaskType.name}
{/if}
</div>
</div>
<div class="h-full flex-row-top w-full justify-center">
<div class="h-full editorBox flex-col">
<Scroller padding={'0 1rem'} noStretch shrink>
{#if selectedTaskType === undefined}
<!-- <div class="flex-col">Navigation</div> -->
<div class="flex-grow h-full">
<div class="p-4 flex-col">
{#if type !== undefined && descriptor !== undefined}
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<ButtonIcon
icon={IconCopy}
size={'small'}
kind={'secondary'}
on:click={(ev) => {
// Do copy of type
}}
/>
<ButtonIcon
icon={IconDelete}
size={'small'}
kind={'secondary'}
on:click={(ev) => {
// Ask for delete
}}
/>
<ButtonIcon
icon={IconMoreV}
size={'small'}
kind={'secondary'}
on:click={(ev) => {
showPopup(ContextMenu, { object: type }, eventToHTMLElement(ev), () => {})
}}
/>
<Breadcrumbs
{items}
size={'large'}
selected={selectedTaskType !== undefined ? 1 : 0}
on:select={(event) => {
if (event.detail === 0) selectTaskType(undefined)
}}
/>
</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>
<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">
<Icon icon={IconFile} size={'small'} />
{projects.length} projects
</div>
</div>
<EditBox
kind={'large-style'}
value={type?.name ?? ''}
on:blur={(evt) => {
if (type !== undefined) {
void client.diffUpdate(type, { name: evt.detail })
}
}}
/>
<div class="p-2">
<EditBox
placeholder={getEmbeddedLabel('Description')}
kind={'small-style'}
bind:value={type.shortDescription}
on:change={() => onShortDescriptionChange(type?.shortDescription ?? '')}
/>
</div>
{#if descriptor?.editor}
<Component is={descriptor.editor} props={{ type }} />
{/if}
<Component is={descriptor.icon} props={{ size: 'large' }} />
</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}
</div>
</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>
</div>
<div class="panelBox flex-col row">
<div class="mt-1">
<TypeClassEditor ofClass={descriptor.baseClass} _class={type.targetClass} />
</div>
<div class="flex-row-center no-word-wrap">
<Icon icon={IconFile} size={'small'} />
{projects.length} projects
</div>
</div>
<EditBox
kind={'large-style'}
value={type?.name ?? ''}
on:blur={(evt) => {
if (type !== undefined) {
void client.diffUpdate(type, { name: evt.detail })
}
}}
/>
<div class="p-2">
<EditBox
placeholder={getEmbeddedLabel('Description')}
kind={'small-style'}
bind:value={type.shortDescription}
on:change={() => onShortDescriptionChange(type?.shortDescription ?? '')}
/>
</div>
{#if descriptor?.editor}
<Component is={descriptor.editor} props={{ type }} />
{/if}
</div>
{:else}
<TaskTypeEditor
taskType={selectedTaskType}
projectType={type}
{taskTypes}
{taskTypeCounter}
{statusCounter}
/>
{/if}
</Scroller>
</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}
</div>
</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>
</div>
<div class="panelBox flex-col row">
<div class="mt-1">
<TypeClassEditor ofClass={descriptor.baseClass} _class={type.targetClass} />
</div>
</div>
</div>
</div>
{:else}
<TaskTypeEditor taskType={selectedTaskType} projectType={type} {taskTypes} {taskTypeCounter} {statusCounter} />
{/if}
</div>
{/if}
</div>
</div>
{/if}
<style lang="scss">
.row {

View File

@ -15,7 +15,7 @@
<script lang="ts">
import core, { Class, Doc, Obj, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { ClassAttributes } from '@hcengineering/setting-resources'
import { ClassAttributesList } from '@hcengineering/setting-resources'
import { Button, Icon, IconAdd } from '@hcengineering/ui'
import { ObjectPresenter } from '@hcengineering/view-resources'
@ -41,7 +41,7 @@
})
$: clazz = client.getHierarchy().getClass(_class)
let mainAttributes: ClassAttributes
let mainAttributes: ClassAttributesList
</script>
<div class="flex flex-between mb-4">
@ -60,7 +60,7 @@
<div class="ml-2 mr-2">
<table class="antiTable mx-2">
<tbody>
<ClassAttributes
<ClassAttributesList
bind:this={mainAttributes}
{_class}
{ofClass}
@ -69,7 +69,7 @@
showCreate={false}
/>
{#each classes as clazz2}
<ClassAttributes
<ClassAttributesList
_class={clazz2._id}
{ofClass}
useOfClassAttributes={false}

View File

@ -1,4 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import core, { Data, Ref } from '@hcengineering/core'
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
import { createQuery, getClient, MessageViewer, SpaceSelector } from '@hcengineering/presentation'
@ -9,17 +10,25 @@
Button,
EditBox,
eventToHTMLElement,
Icon,
IconAdd,
IconEdit,
Label,
showPopup
showPopup,
Header,
Breadcrumb,
Separator,
defineSeparators,
settingsSeparators
} from '@hcengineering/ui'
import { getActions as getContributedActions, TreeItem, TreeNode } from '@hcengineering/view-resources'
import templatesPlugin from '../plugin'
import CreateTemplateCategory from './CreateTemplateCategory.svelte'
import FieldPopup from './FieldPopup.svelte'
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
const client = getClient()
const query = createQuery()
const spaceQ = createQuery()
@ -138,17 +147,22 @@
}
let space: Ref<TemplateCategory> | undefined = undefined
defineSeparators('workspaceSettings', settingsSeparators)
</script>
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={templatesPlugin.icon.Templates} size={'medium'} /></div>
<div class="ac-header__title"><Label label={templatesPlugin.string.Templates} /></div>
</div>
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb
icon={templatesPlugin.icon.Templates}
label={templatesPlugin.string.Templates}
size={'large'}
isCurrent
/>
</Header>
<div class="ac-body columns clear-mins">
<div class="ac-column">
<div id="create-template" class="flex-between trans-title mb-3">
<div class="hulyComponent-content__container columns">
<div class="hulyComponent-content__column">
<div id="create-template" class="flex-between trans-title m-3">
<Button
icon={templatesPlugin.icon.Template}
label={templatesPlugin.string.CreateTemplate}
@ -179,79 +193,81 @@
{/each}
</div>
</div>
<div class="ac-column max template-container">
{#if newTemplate}
<div class="flex-between mr-4">
<span class="trans-title mb-3">
<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">
{#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>
</div>
</div>

View File

@ -0,0 +1,28 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Space } from '@hcengineering/core'
import { Header, Breadcrumb } from '@hcengineering/ui'
import tracker from '../plugin'
import EditRelatedTargets from './EditRelatedTargets.svelte'
export let value: Space | undefined
export let visibleNav: boolean = true
const dispatch = createEventDispatcher()
</script>
<div class="hulyComponent">
<Header minimize={!visibleNav} on:resize={(event) => dispatch('change', event.detail)}>
<Breadcrumb icon={tracker.icon.Relations} label={tracker.string.RelatedIssues} size={'large'} isCurrent />
</Header>
<EditRelatedTargets {value} />
</div>
<style lang="scss">
.bordered {
padding: 0.25rem 0.5rem;
background-color: var(--theme-comp-header-color);
border: 1px solid var(--theme-divider-color);
border-radius: 0.25rem;
}
</style>

View File

@ -44,6 +44,7 @@ import ProjectComponents from './components/components/ProjectComponents.svelte'
import CreateIssue from './components/CreateIssue.svelte'
import EditRelatedTargets from './components/EditRelatedTargets.svelte'
import EditRelatedTargetsPopup from './components/EditRelatedTargetsPopup.svelte'
import SettingsRelatedTargets from './components/SettingsRelatedTargets.svelte'
import Inbox from './components/inbox/Inbox.svelte'
import AssigneeEditor from './components/issues/AssigneeEditor.svelte'
import DueDatePresenter from './components/issues/DueDatePresenter.svelte'
@ -506,6 +507,7 @@ export default async (): Promise<Resources> => ({
ComponentFilterValuePresenter,
EditRelatedTargets,
EditRelatedTargetsPopup,
SettingsRelatedTargets,
TimePresenter,
EstimationValueEditor,
IssueStatusIcon,

View File

@ -53,7 +53,8 @@
resolvedLocationStore,
setResolvedLocation,
showPopup,
workbenchSeparators
workbenchSeparators,
IconSettings
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import {
@ -78,7 +79,6 @@
import Navigator from './Navigator.svelte'
import SelectWorkspaceMenu from './SelectWorkspaceMenu.svelte'
import SpaceView from './SpaceView.svelte'
import IconSettings from './icons/Settings.svelte'
import TopMenu from './icons/TopMenu.svelte'
let contentPanel: HTMLElement
@ -640,7 +640,11 @@
/>
</clipPath>
</svg>
<div class="workbench-container" style:flex-direction={appsDirection === 'horizontal' ? 'column-reverse' : 'row'}>
<div
class="workbench-container"
class:setting-app={currentApplication?.alias === 'setting'}
style:flex-direction={appsDirection === 'horizontal' ? 'column-reverse' : 'row'}
>
<div class="antiPanel-application {appsDirection}" class:lastDivider={!visibleNav}>
<div
class="hamburger-container clear-mins"
@ -750,7 +754,7 @@
application: currentApplication?._id
}}
/>
<div class="workbench-container">
<div class="workbench-container inner">
{#if currentApplication && navigatorModel && navigator && visibleNav}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
@ -879,6 +883,28 @@
width: 100%;
height: 100%;
touch-action: none;
&:not(.setting-app, .inner) {
border-top: 1px solid var(--theme-navpanel-divider);
}
&.setting-app {
position: relative;
background-color: var(--theme-statusbar-color);
border-radius: var(--medium-BorderRadius);
&::after {
position: absolute;
content: '';
inset: 0;
border: 1px solid var(--theme-divider-color);
border-radius: var(--medium-BorderRadius);
pointer-events: none;
}
.antiPanel-application {
border-radius: var(--medium-BorderRadius) 0 0 var(--medium-BorderRadius);
border-right: none;
}
}
}
.hamburger-container {