Update EditDoc layout (#2402)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2022-11-30 08:53:49 +03:00 committed by GitHub
parent 807a6f82b2
commit a8a8f80d62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 277 additions and 109 deletions

View File

@ -225,7 +225,8 @@ export function createModel (builder: Builder): void {
)
builder.mixin(contact.class.Member, core.class.Class, view.mixin.ObjectEditor, {
editor: contact.component.EditMember
editor: contact.component.EditMember,
pinned: true
})
builder.createDoc(
@ -253,15 +254,18 @@ export function createModel (builder: Builder): void {
)
builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectEditor, {
editor: contact.component.EditPerson
editor: contact.component.EditPerson,
pinned: true
})
builder.mixin(contact.class.Employee, core.class.Class, view.mixin.ObjectEditor, {
editor: contact.component.EditPerson
editor: contact.component.EditPerson,
pinned: true
})
builder.mixin(contact.class.Organization, core.class.Class, view.mixin.ObjectEditor, {
editor: contact.component.EditOrganization
editor: contact.component.EditOrganization,
pinned: true
})
builder.mixin(contact.class.Organization, core.class.Class, view.mixin.AttributeEditor, {

View File

@ -136,6 +136,7 @@ export class TAttributePresenter extends TClass implements AttributePresenter {
@Mixin(view.mixin.ObjectEditor, core.class.Class)
export class TObjectEditor extends TClass implements ObjectEditor {
editor!: AnyComponent
pinned?: boolean
}
@Mixin(view.mixin.ObjectEditorHeader, core.class.Class)

View File

@ -87,7 +87,7 @@
{#if $$slots.header}
<div class="header-row between">
{#if $$slots.header}<slot name="header" />{/if}
<div class="buttons-group xsmall-gap ml-4" style:align-self={'flex-end'}>
<div class="buttons-group xsmall-gap ml-4" style:align-self={'flex-start'}>
<slot name="tools" />
</div>
</div>
@ -95,6 +95,9 @@
{#if $$slots['custom-attributes'] && isCustomAttr}
{#if isSub}<div class="header-row"><slot name="custom-attributes" direction="row" /></div>{/if}
{/if}
{#if $$slots.subheader}
<slot name="subheader" />
{/if}
</svelte:fragment>
<svelte:fragment slot="aside">

View File

@ -368,6 +368,7 @@ input.search {
.step-lr25 + .step-lr25 { margin-left: .25rem; }
.step-lr75 + .step-lr75 { margin-left: .75rem; }
.step-tb75 + .step-tb75 { margin-top: .75rem; }
.step-tb-6 + .step-tb-6 { margin-top: 1.5rem; }
.ml-0-5 { margin-left: .125rem; }
.ml-1 { margin-left: .25rem; }
@ -541,6 +542,7 @@ input.search {
.max-h-50 { max-height: 12.5rem; }
.max-h-60 { max-height: 15rem; }
.max-h-125 { max-height: 31.25rem; }
.max-h-30vh { max-height: 30vh; }
.clear-mins {
min-width: 0;
min-height: 0;

View File

@ -161,7 +161,7 @@
flex-shrink: 0;
padding: .5rem .75rem .75rem;
width: 100%;
min-width: 700px;
min-width: 320px;
}
&.bottom-divider { border-bottom: 1px solid var(--divider-color); }
@ -179,7 +179,7 @@
}
.popupPanel-body__mobile-content {
padding: .5rem;
min-width: 700px;
min-width: 320px;
}
&__aside {

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import { Class, Doc, DocumentQuery, Ref, Space } from '@hcengineering/core'
import { Icon, Label, Spinner } from '@hcengineering/ui'
import { Icon, Label, Spinner, resizeObserver, Scroller } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { Table } from '@hcengineering/view-resources'
import attachment from '../plugin'
@ -34,9 +34,10 @@
let inputFile: HTMLInputElement
let loading = 0
let dragover = false
let wSection: number
</script>
<div class="antiSection">
<div class="antiSection" use:resizeObserver={(element) => (wSection = element.clientWidth)}>
<div class="antiSection-header">
<div class="antiSection-header__icon">
<Icon icon={IconAttachment} size={'small'} />
@ -70,6 +71,29 @@
</div>
</div>
</AttachmentDroppable>
{:else if wSection < 640}
<Scroller horizontal>
<Table
_class={attachment.class.Attachment}
config={[
'',
'description',
{
key: 'pinned',
presenter: view.component.BooleanTruePresenter,
label: attachment.string.Pinned,
sortingKey: 'pinned'
},
'lastModified'
]}
options={{ sort: { pinned: -1 } }}
query={{ ...query, attachedTo: objectId }}
loadingProps={{ length: attachments ?? 0 }}
on:content={(evt) => {
attachments = evt.detail.length
}}
/>
</Scroller>
{:else}
<Table
_class={attachment.class.Attachment}

View File

@ -15,7 +15,7 @@
<script lang="ts">
import type { Ref } from '@hcengineering/core'
import type { Customer } from '@hcengineering/lead'
import { Button, IconAdd, Label, showPopup } from '@hcengineering/ui'
import { Button, IconAdd, Label, showPopup, resizeObserver, Scroller } from '@hcengineering/ui'
import { Table } from '@hcengineering/view-resources'
import lead from '../plugin'
import CreateLead from './CreateLead.svelte'
@ -27,9 +27,10 @@
const createLead = (ev: MouseEvent): void => {
showPopup(CreateLead, { candidate: objectId, preserveCandidate: true }, ev.target as HTMLElement)
}
let wSection: number
</script>
<div class="antiSection">
<div class="antiSection" use:resizeObserver={(element) => (wSection = element.clientWidth)}>
<div class="antiSection-header">
<span class="antiSection-header__title">
<Label label={lead.string.Leads} />
@ -37,12 +38,23 @@
<Button icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={createLead} />
</div>
{#if leads !== undefined && leads > 0}
<Table
_class={lead.class.Lead}
config={['', '$lookup.state', '$lookup.doneState']}
query={{ attachedTo: objectId }}
{loadingProps}
/>
{#if wSection < 640}
<Scroller horizontal>
<Table
_class={lead.class.Lead}
config={['', '$lookup.state', '$lookup.doneState']}
query={{ attachedTo: objectId }}
{loadingProps}
/>
</Scroller>
{:else}
<Table
_class={lead.class.Lead}
config={['', '$lookup.state', '$lookup.doneState']}
query={{ attachedTo: objectId }}
{loadingProps}
/>
{/if}
{:else}
<div class="antiSection-empty solid flex-col-center mt-3">
<span class="text-sm dark-color">

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import type { Doc, Ref } from '@hcengineering/core'
import { Button, IconAdd, Label, showPopup, Icon } from '@hcengineering/ui'
import { Button, IconAdd, Label, showPopup, Icon, Scroller, resizeObserver } from '@hcengineering/ui'
import { BuildModelKey } from '@hcengineering/view'
import { Table } from '@hcengineering/view-resources'
import recruit from '../plugin'
@ -38,9 +38,10 @@
'$lookup.state',
'$lookup.doneState'
]
let wSection: number
</script>
<div class="antiSection">
<div class="antiSection" use:resizeObserver={(element) => (wSection = element.clientWidth)}>
<div class="antiSection-header">
<div class="antiSection-header__icon">
<Icon icon={IconApplication} size={'small'} />
@ -51,12 +52,23 @@
<Button id="appls.add" icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={createApp} />
</div>
{#if applications > 0}
<Table
_class={recruit.class.Applicant}
{config}
query={{ attachedTo: objectId }}
loadingProps={{ length: applications }}
/>
{#if wSection < 640}
<Scroller horizontal>
<Table
_class={recruit.class.Applicant}
{config}
query={{ attachedTo: objectId }}
loadingProps={{ length: applications }}
/>
</Scroller>
{:else}
<Table
_class={recruit.class.Applicant}
{config}
query={{ attachedTo: objectId }}
loadingProps={{ length: applications }}
/>
{/if}
{:else}
<div class="antiSection-empty solid flex-col-center mt-3">
<div class="caption-color">

View File

@ -17,6 +17,7 @@
import { createEventDispatcher, onMount } from 'svelte'
import { createQuery } from '@hcengineering/presentation'
import type { Candidate, Applicant, Vacancy } from '@hcengineering/recruit'
import { Scroller } from '@hcengineering/ui'
import CandidateCard from './CandidateCard.svelte'
import VacancyCard from './VacancyCard.svelte'
import ExpandRightDouble from './icons/ExpandRightDouble.svelte'
@ -51,11 +52,13 @@
</script>
{#if object !== undefined && candidate !== undefined}
<div class="flex-between">
<div class="card"><CandidateCard {candidate} on:click /></div>
<div class="arrows"><ExpandRightDouble /></div>
<div class="card"><VacancyCard {vacancy} /></div>
</div>
<Scroller horizontal>
<div class="flex-between min-w-min">
<div class="card"><CandidateCard {candidate} on:click /></div>
<div class="flex-center arrows"><ExpandRightDouble /></div>
<div class="card"><VacancyCard {vacancy} /></div>
</div>
</Scroller>
<div class="mt-6">
<Reviews objectId={candidate._id} reviews={candidate.reviews ?? 0} label={recruit.string.TalentReviews} />
</div>
@ -63,11 +66,14 @@
<style lang="scss">
.card {
flex-shrink: 0;
align-self: stretch;
width: calc(50% - 3rem);
width: calc(50% - 5rem);
min-width: max-content;
min-height: 16rem;
}
.arrows {
flex-shrink: 0;
width: 4rem;
}
</style>

View File

@ -66,7 +66,7 @@
icon={clazz.icon}
title={object.name}
subtitle={object.description}
isHeader={false}
isHeader={true}
isAside={true}
{object}
on:close={() => {
@ -88,7 +88,7 @@
{/if}
</svelte:fragment>
<svelte:fragment slot="header">
<svelte:fragment slot="subheader">
<span class="fs-title flex-grow">
<EditBox
bind:value={object.name}
@ -102,7 +102,7 @@
/>
</span>
</svelte:fragment>
<svelte:fragment slot="tools">
<svelte:fragment slot="utils">
<div class="p-1">
<Button icon={IconMoreH} kind={'transparent'} size={'medium'} on:click={showMenu} />
</div>

View File

@ -17,7 +17,7 @@
import core from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import calendar from '@hcengineering/calendar'
import { Button, IconAdd, Label, showPopup } from '@hcengineering/ui'
import { Button, IconAdd, Label, showPopup, resizeObserver, Scroller } from '@hcengineering/ui'
import { Table } from '@hcengineering/view-resources'
import recruit from '../../plugin'
import FileDuo from '../icons/FileDuo.svelte'
@ -30,9 +30,10 @@
const createApp = (ev: MouseEvent): void => {
showPopup(CreateReview, { candidate: objectId, preserveCandidate: true }, ev.target as HTMLElement)
}
let wSection: number
</script>
<div class="antiSection">
<div class="antiSection" use:resizeObserver={(element) => (wSection = element.clientWidth)}>
<div class="antiSection-header">
<span class="antiSection-header__title">
<Label {label} />
@ -40,27 +41,58 @@
<Button icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={createApp} />
</div>
{#if reviews > 0}
<Table
_class={recruit.class.Review}
config={[
'',
'verdict',
{
key: '',
presenter: recruit.component.OpinionsPresenter,
label: recruit.string.Opinions,
sortingKey: 'opinions'
},
{ key: '', presenter: calendar.component.DateTimePresenter, label: calendar.string.Date, sortingKey: 'date' }
]}
options={{
lookup: {
space: core.class.Space
}
}}
query={{ attachedTo: objectId }}
loadingProps={{ length: reviews }}
/>
{#if wSection < 640}
<Scroller horizontal>
<Table
_class={recruit.class.Review}
config={[
'',
'verdict',
{
key: '',
presenter: recruit.component.OpinionsPresenter,
label: recruit.string.Opinions,
sortingKey: 'opinions'
},
{
key: '',
presenter: calendar.component.DateTimePresenter,
label: calendar.string.Date,
sortingKey: 'date'
}
]}
options={{
lookup: {
space: core.class.Space
}
}}
query={{ attachedTo: objectId }}
loadingProps={{ length: reviews }}
/>
</Scroller>
{:else}
<Table
_class={recruit.class.Review}
config={[
'',
'verdict',
{
key: '',
presenter: recruit.component.OpinionsPresenter,
label: recruit.string.Opinions,
sortingKey: 'opinions'
},
{ key: '', presenter: calendar.component.DateTimePresenter, label: calendar.string.Date, sortingKey: 'date' }
]}
options={{
lookup: {
space: core.class.Space
}
}}
query={{ attachedTo: objectId }}
loadingProps={{ length: reviews }}
/>
{/if}
{:else}
<div class="antiSection-empty solid flex-col-center mt-3">
<div class="caption-color">

View File

@ -16,7 +16,7 @@
import type { Ref, Space, Doc, Class } from '@hcengineering/core'
import type { TodoItem } from '@hcengineering/task'
import { createQuery } from '@hcengineering/presentation'
import { Button, IconAdd, showPopup, Label } from '@hcengineering/ui'
import { Button, IconAdd, showPopup, Label, resizeObserver, Scroller } from '@hcengineering/ui'
import CreateTodo from './CreateTodo.svelte'
import { Table } from '@hcengineering/view-resources'
@ -37,9 +37,10 @@
const createApp = (ev: MouseEvent): void => {
showPopup(CreateTodo, { objectId, _class, space }, ev.target as HTMLElement)
}
let wSection: number
</script>
<div class="antiSection">
<div class="antiSection" use:resizeObserver={(element) => (wSection = element.clientWidth)}>
<div class="antiSection-header">
<span class="antiSection-header__title">
<Label label={plugin.string.Todos} />
@ -47,20 +48,39 @@
<Button icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={createApp} />
</div>
{#if todos.length > 0}
<Table
_class={task.class.TodoItem}
config={[
{ key: '', label: plugin.string.TodoName },
'dueTo',
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
]}
options={{
sort: {
rank: 1
}
}}
query={{ attachedTo: objectId }}
/>
{#if wSection < 640}
<Scroller horizontal>
<Table
_class={task.class.TodoItem}
config={[
{ key: '', label: plugin.string.TodoName },
'dueTo',
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
]}
options={{
sort: {
rank: 1
}
}}
query={{ attachedTo: objectId }}
/>
</Scroller>
{:else}
<Table
_class={task.class.TodoItem}
config={[
{ key: '', label: plugin.string.TodoName },
'dueTo',
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
]}
options={{
sort: {
rank: 1
}
}}
query={{ attachedTo: objectId }}
/>
{/if}
{:else}
<div class="antiSection-empty solid flex-col-center mt-3">
<span class="text-sm over-underline" on:click={createApp}>

View File

@ -36,6 +36,7 @@
import ActionContext from './ActionContext.svelte'
import DocAttributeBar from './DocAttributeBar.svelte'
import UpDownNavigator from './UpDownNavigator.svelte'
import IconMixin from './icons/Mixin.svelte'
export let _id: Ref<Doc>
export let _class: Ref<Class<Doc>>
@ -131,14 +132,19 @@
fieldEditors = editors.sort((a, b) => AttributeCategoryOrder[a.category] - AttributeCategoryOrder[b.category])
}
async function getEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent> {
interface MixinEditor {
editor: AnyComponent
pinned?: boolean
}
async function getEditor (_class: Ref<Class<Doc>>): Promise<MixinEditor> {
const clazz = hierarchy.getClass(_class)
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditor)
if (editorMixin?.editor == null && clazz.extends != null) return getEditor(clazz.extends)
return editorMixin.editor
return { editor: editorMixin.editor, pinned: editorMixin?.pinned }
}
let mainEditor: AnyComponent | undefined
let mainEditor: MixinEditor | undefined
$: getEditorOrDefault(realObjectClass, showAllMixins)
async function getEditorOrDefault (_class: Ref<Class<Doc>>, showAllMixins: boolean): Promise<void> {
@ -252,7 +258,7 @@
{icon}
{title}
{object}
isHeader={false}
isHeader={mainEditor?.pinned ?? false}
isAside={true}
bind:panelWidth
bind:innerWidth
@ -265,7 +271,7 @@
<UpDownNavigator element={object} />
</svelte:fragment>
<svelte:fragment slot="tools">
<svelte:fragment slot="utils">
<div class="p-1">
<Button icon={IconMoreH} kind={'transparent'} size={'medium'} on:click={showMenu} />
</div>
@ -282,17 +288,7 @@
}}
>
<svelte:fragment slot="content">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.66602" y="2.66663" width="10.6667" height="4.66667" rx="1" stroke="white" />
<path
d="M2.66602 11.3334C2.66602 10.3906 2.66602 9.91916 2.95891 9.62627C3.2518 9.33337 3.72321 9.33337 4.66602 9.33337H6.66602V11.3334C6.66602 12.2762 6.66602 12.7476 6.37312 13.0405C6.37312 13.0405 6.37312 13.0405 6.37312 13.0405C6.08023 13.3334 5.60882 13.3334 4.66602 13.3334V13.3334C3.72321 13.3334 3.2518 13.3334 2.95891 13.0405C2.95891 13.0405 2.95891 13.0405 2.95891 13.0405C2.66602 12.7476 2.66602 12.2762 2.66602 11.3334V11.3334Z"
stroke="white"
/>
<path
d="M9.33398 9.33337H11.334C12.2768 9.33337 12.7482 9.33337 13.0411 9.62627C13.334 9.91916 13.334 10.3906 13.334 11.3334V11.3334C13.334 12.2762 13.334 12.7476 13.0411 13.0405C12.7482 13.3334 12.2768 13.3334 11.334 13.3334V13.3334C10.3912 13.3334 9.91977 13.3334 9.62688 13.0405C9.33398 12.7476 9.33398 12.2762 9.33398 11.3334V9.33337Z"
stroke="white"
/>
</svg>
<IconMixin size={'small'} />
</svelte:fragment>
</Button>
</div>
@ -317,23 +313,45 @@
{/if}
</svelte:fragment>
{#if mainEditor}
<Component
is={mainEditor}
props={{ object }}
on:open={(ev) => {
ignoreKeys = ev.detail.ignoreKeys
ignoreMixins = new Set(ev.detail.ignoreMixins)
allowedCollections = ev.detail.allowedCollections ?? []
collectionArrays = ev.detail.collectionArrays ?? []
getMixins(parentClass, object, showAllMixins)
updateKeys(showAllMixins)
}}
/>
<svelte:fragment slot="subheader">
{#if mainEditor && mainEditor.pinned}
<div class="flex-col flex-grow step-tb-6">
<Component
is={mainEditor.editor}
props={{ object }}
on:open={(ev) => {
ignoreKeys = ev.detail.ignoreKeys
ignoreMixins = new Set(ev.detail.ignoreMixins)
allowedCollections = ev.detail.allowedCollections ?? []
collectionArrays = ev.detail.collectionArrays ?? []
getMixins(parentClass, object, showAllMixins)
updateKeys(showAllMixins)
}}
/>
</div>
{/if}
</svelte:fragment>
{#if mainEditor && !mainEditor.pinned}
<div class="flex-col flex-grow flex-no-shrink step-tb-6">
<Component
is={mainEditor.editor}
props={{ object }}
on:open={(ev) => {
ignoreKeys = ev.detail.ignoreKeys
ignoreMixins = new Set(ev.detail.ignoreMixins)
allowedCollections = ev.detail.allowedCollections ?? []
collectionArrays = ev.detail.collectionArrays ?? []
getMixins(parentClass, object, showAllMixins)
updateKeys(showAllMixins)
}}
/>
</div>
{/if}
{#each fieldEditors as collection}
{#if collection.editor}
<div class="mt-6">
<div class="step-tb-6">
<Component
is={collection.editor}
props={{

View File

@ -0,0 +1,32 @@
<!--
//
// 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">
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="M12.3,7.8H3.7c-0.8,0-1.5-0.7-1.5-1.5V3.7c0-0.8,0.7-1.5,1.5-1.5h8.7c0.8,0,1.5,0.7,1.5,1.5v2.7C13.8,7.2,13.2,7.8,12.3,7.8z M3.7,3.2c-0.3,0-0.5,0.2-0.5,0.5v2.7c0,0.3,0.2,0.5,0.5,0.5h8.7c0.3,0,0.5-0.2,0.5-0.5V3.7c0-0.3-0.2-0.5-0.5-0.5H3.7z"
/>
<path
d="M4.7,13.8c-1,0-1.6,0-2.1-0.4s-0.4-1-0.4-2.1s0-1.6,0.4-2.1s1-0.4,2.1-0.4h2.5v2.5c0,1,0,1.6-0.4,2.1S5.7,13.8,4.7,13.8z M4.7,9.8c-0.7,0-1.2,0-1.4,0.1s-0.1,0.6-0.1,1.4s0,1.2,0.1,1.4s0.6,0.1,1.4,0.1s1.2,0,1.4-0.1s0.1-0.6,0.1-1.4V9.8H4.7z"
/>
<path
d="M11.3,13.8c-1,0-1.6,0-2.1-0.4s-0.4-1-0.4-2.1V8.8h2.5c1,0,1.6,0,2.1,0.4s0.4,1,0.4,2.1s0,1.6-0.4,2.1S12.4,13.8,11.3,13.8z M9.8,9.8v1.5c0,0.7,0,1.2,0.1,1.4s0.6,0.1,1.4,0.1s1.2,0,1.4-0.1s0.1-0.6,0.1-1.4s0-1.2-0.1-1.4s-0.6-0.1-1.4-0.1H9.8z"
/>
</svg>

View File

@ -1,6 +1,6 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// 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
@ -12,10 +12,11 @@
//
// 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 = 'var(--theme-caption-color)'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">

View File

@ -133,6 +133,7 @@ export interface AttributePresenter extends Class<Doc> {
*/
export interface ObjectEditor extends Class<Doc> {
editor: AnyComponent
pinned?: boolean
}
/**