HR: Issue fixes (#1891)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-05-28 14:33:11 +07:00 committed by GitHub
parent 9f9003ec68
commit a2858c845e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 288 additions and 51 deletions

View File

@ -34,6 +34,7 @@
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-view": "~0.6.0",
"@anticrm/activity": "~0.6.0",
"@anticrm/model-preference": "~0.6.0"
"@anticrm/model-preference": "~0.6.0",
"@anticrm/view": "~0.6.0"
}
}

View File

@ -16,10 +16,10 @@
import activity from '@anticrm/activity'
import type { Attachment, Photo, SavedAttachments } from '@anticrm/attachment'
import { Domain, IndexKind, Ref } from '@anticrm/core'
import { Builder, Index, Model, Prop, TypeRef, TypeString, TypeTimestamp, UX } from '@anticrm/model'
import { Builder, Index, Model, Prop, TypeBoolean, TypeRef, TypeString, TypeTimestamp, UX } from '@anticrm/model'
import core, { TAttachedDoc } from '@anticrm/model-core'
import view from '@anticrm/model-view'
import preference, { TPreference } from '@anticrm/model-preference'
import view, { createAction } from '@anticrm/model-view'
import attachment from './plugin'
export { attachmentOperation } from './migration'
@ -45,6 +45,12 @@ export class TAttachment extends TAttachedDoc implements Attachment {
@Prop(TypeTimestamp(), attachment.string.Date)
lastModified!: number
@Prop(TypeString(), attachment.string.Description)
description!: string
@Prop(TypeBoolean(), attachment.string.Pinned)
pinned!: boolean
}
@Model(attachment.class.Photo, attachment.class.Attachment)
@ -89,6 +95,64 @@ export function createModel (builder: Builder): void {
},
attachment.ids.TxAttachmentCreate
)
builder.createDoc(
view.class.ActionCategory,
core.space.Model,
{ label: attachment.string.Attachments, visible: true },
attachment.category.Attachments
)
createAction(builder, {
action: view.actionImpl.ShowEditor,
actionProps: {
attribute: 'description'
},
label: attachment.string.Description,
icon: view.icon.Open,
input: 'focus',
category: attachment.category.Attachments,
target: attachment.class.Attachment,
context: {
mode: ['context', 'browser']
}
})
createAction(builder, {
action: view.actionImpl.UpdateDocument,
actionProps: {
key: 'pinned',
value: true
},
query: {
pinned: { $in: [false, undefined, null] }
},
label: attachment.string.PinAttachment,
input: 'focus',
category: attachment.category.Attachments,
target: attachment.class.Attachment,
context: {
mode: ['context', 'browser']
}
})
createAction(builder, {
action: view.actionImpl.UpdateDocument,
actionProps: {
key: 'pinned',
value: false
},
query: {
pinned: true
},
label: attachment.string.UnPinAttachment,
input: 'focus',
category: attachment.category.Attachments,
target: attachment.class.Attachment,
context: {
mode: ['context', 'browser']
}
})
}
export default attachment

View File

@ -13,13 +13,14 @@
// limitations under the License.
//
import type { TxViewlet } from '@anticrm/activity'
import { attachmentId } from '@anticrm/attachment'
import attachment from '@anticrm/attachment-resources/src/plugin'
import type { Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import type { Ref } from '@anticrm/core'
import type { AnyComponent } from '@anticrm/ui'
import type { TxViewlet } from '@anticrm/activity'
import type { ActionCategory } from '@anticrm/view'
export default mergeIds(attachmentId, attachment, {
component: {
@ -34,12 +35,18 @@ export default mergeIds(attachmentId, attachment, {
Type: '' as IntlString,
Photo: '' as IntlString,
Date: '' as IntlString,
SavedAttachments: '' as IntlString
SavedAttachments: '' as IntlString,
Description: '' as IntlString,
PinAttachment: '' as IntlString,
UnPinAttachment: '' as IntlString
},
ids: {
TxAttachmentCreate: '' as Ref<TxViewlet>
},
activity: {
TxAttachmentCreate: '' as AnyComponent
},
category: {
Attachments: '' as Ref<ActionCategory>
}
})

View File

@ -92,6 +92,20 @@ async function createDefaults (tx: TxOperations): Promise<void> {
await createSequence(tx, recruit.class.Opinion)
await createSequence(tx, recruit.class.Applicant)
await createDefaultKanbanTemplate(tx)
await createOrUpdate(
tx,
tags.class.TagCategory,
tags.space.Tags,
{
icon: tags.icon.Tags,
label: 'Text Label',
targetClass: recruit.class.Applicant,
tags: [],
default: true
},
recruit.category.OtherLabel
)
}
async function createDefaultKanbanTemplate (tx: TxOperations): Promise<Ref<KanbanTemplate>> {

View File

@ -68,14 +68,16 @@ export function classPresenter (
builder: Builder,
_class: Ref<Class<Doc>>,
presenter: AnyComponent,
editor?: AnyComponent
editor?: AnyComponent,
popup?: AnyComponent
): void {
builder.mixin(_class, core.class.Class, view.mixin.AttributePresenter, {
presenter
})
if (editor !== undefined) {
builder.mixin(_class, core.class.Class, view.mixin.AttributeEditor, {
editor
editor,
popup
})
}
}
@ -267,7 +269,13 @@ export function createModel (builder: Builder): void {
TLinkPresenter
)
classPresenter(builder, core.class.TypeString, view.component.StringPresenter, view.component.StringEditor)
classPresenter(
builder,
core.class.TypeString,
view.component.StringPresenter,
view.component.StringEditor,
view.component.StringEditorPopup
)
classPresenter(builder, core.class.TypeIntlString, view.component.IntlStringPresenter)
classPresenter(builder, core.class.TypeNumber, view.component.NumberPresenter, view.component.NumberEditor)
classPresenter(builder, core.class.TypeMarkup, view.component.HTMLPresenter)

View File

@ -64,6 +64,7 @@ export default mergeIds(viewId, view, {
ValueFilter: '' as AnyComponent,
TimestampFilter: '' as AnyComponent,
StringEditor: '' as AnyComponent,
StringEditorPopup: '' as AnyComponent,
StringPresenter: '' as AnyComponent,
IntlStringPresenter: '' as AnyComponent,
NumberEditor: '' as AnyComponent,

View File

@ -76,7 +76,7 @@ const predicates: Record<string, PredicateFactory> = {
$lte: (o, propertyKey) => {
return (docs) => execPredicate(docs, propertyKey, (value) => value <= o)
},
$exist: (o, propertyKey) => {
$exists: (o, propertyKey) => {
return (docs) => execPredicate(docs, propertyKey, (value) => (value !== undefined) === o)
}
}

View File

@ -28,7 +28,7 @@ export type QuerySelector<T> = {
$gte?: T extends number ? number : never
$lt?: T extends number ? number : never
$lte?: T extends number ? number : never
$exist?: boolean
$exists?: boolean
$like?: string
$regex?: string
$options?: string

View File

@ -44,6 +44,10 @@
"FileBrowserTypeFilterPDFs": "PDFs",
"AddAttachmentToSaved": "Add attachment to saved",
"RemoveAttachmentFromSaved": "Remove attachment from saved",
"DeleteFile": "Delete file"
"DeleteFile": "Delete file",
"Description": "Description",
"Pinned": "Important",
"PinAttachment": "Mark important",
"UnPinAttachment": "Mark less important"
}
}

View File

@ -44,6 +44,10 @@
"FileBrowserTypeFilterPDFs": "PDF-файлы",
"AddAttachmentToSaved": "Добавить вложение в сохраненные",
"RemoveAttachmentFromSaved": "Удалить вложение из сохраненных",
"DeleteFile": "Удалить файл"
"DeleteFile": "Удалить файл",
"Description": "Описание",
"Pinned": "Важное",
"PinAttachment": "Пометить как важное",
"UnPinAttachment": "Убрать пометку важное"
}
}

View File

@ -15,13 +15,12 @@
-->
<script lang="ts">
import { Class, Doc, Ref, Space } from '@anticrm/core'
import { Label, Spinner } from '@anticrm/ui'
import view from '@anticrm/view'
import { Table } from '@anticrm/view-resources'
import attachment from '../plugin'
import AddAttachment from './AddAttachment.svelte'
import AttachmentDroppable from './AttachmentDroppable.svelte'
import UploadDuo from './icons/UploadDuo.svelte'
export let objectId: Ref<Doc>
@ -62,8 +61,18 @@
{:else}
<Table
_class={attachment.class.Attachment}
config={['', 'lastModified']}
options={{}}
config={[
'',
'description',
{
key: 'pinned',
presenter: view.component.BooleanTruePresenter,
label: attachment.string.Pinned,
sortingKey: 'pinned'
},
'lastModified'
]}
options={{ sort: { pinned: -1 } }}
query={{ attachedTo: objectId }}
loadingProps={{ length: attachments ?? 0 }}
/>

View File

@ -13,25 +13,25 @@
// limitations under the License.
//
import AddAttachment from './components/AddAttachment.svelte'
import AttachmentDroppable from './components/AttachmentDroppable.svelte'
import AttachmentsPresenter from './components/AttachmentsPresenter.svelte'
import AttachmentPresenter from './components/AttachmentPresenter.svelte'
import AttachmentGalleryPresenter from './components/AttachmentGalleryPresenter.svelte'
import AttachmentDocList from './components/AttachmentDocList.svelte'
import AttachmentList from './components/AttachmentList.svelte'
import AttachmentRefInput from './components/AttachmentRefInput.svelte'
import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte'
import Attachments from './components/Attachments.svelte'
import FileBrowser from './components/FileBrowser.svelte'
import Photos from './components/Photos.svelte'
import FileDownload from './components/icons/FileDownload.svelte'
import { uploadFile, deleteFile } from './utils'
import attachment, { Attachment } from '@anticrm/attachment'
import { ObjQueryType, SortingOrder, SortingQuery } from '@anticrm/core'
import { IntlString, Resources } from '@anticrm/platform'
import preference from '@anticrm/preference'
import { getClient } from '@anticrm/presentation'
import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte'
import AddAttachment from './components/AddAttachment.svelte'
import AttachmentDocList from './components/AttachmentDocList.svelte'
import AttachmentDroppable from './components/AttachmentDroppable.svelte'
import AttachmentGalleryPresenter from './components/AttachmentGalleryPresenter.svelte'
import AttachmentList from './components/AttachmentList.svelte'
import AttachmentPresenter from './components/AttachmentPresenter.svelte'
import AttachmentRefInput from './components/AttachmentRefInput.svelte'
import Attachments from './components/Attachments.svelte'
import AttachmentsPresenter from './components/AttachmentsPresenter.svelte'
import FileBrowser from './components/FileBrowser.svelte'
import FileDownload from './components/icons/FileDownload.svelte'
import Photos from './components/Photos.svelte'
import { deleteFile, uploadFile } from './utils'
export {
AddAttachment,

View File

@ -14,10 +14,9 @@
// limitations under the License.
//
import { mergeIds } from '@anticrm/platform'
import type { IntlString } from '@anticrm/platform'
import attachment, { attachmentId } from '@anticrm/attachment'
import type { IntlString } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import { ViewAction } from '@anticrm/view'
export default mergeIds(attachmentId, attachment, {
@ -42,7 +41,8 @@ export default mergeIds(attachmentId, attachment, {
FileBrowserSortSmallest: '' as IntlString,
FileBrowserSortBiggest: '' as IntlString,
AddAttachmentToSaved: '' as IntlString,
RemoveAttachmentFromSaved: '' as IntlString
RemoveAttachmentFromSaved: '' as IntlString,
Pinned: '' as IntlString
},
actionImpl: {
AddAttachmentToSaved: '' as ViewAction,

View File

@ -29,6 +29,8 @@ export interface Attachment extends AttachedDoc {
size: number
type: string
lastModified: number
description?: string
pinned?: boolean // If defined and true, will be shown in top of attachments collection
}
/**

View File

@ -112,7 +112,8 @@ export default mergeIds(recruitId, recruit, {
},
category: {
Other: '' as Ref<TagCategory>,
Category: '' as Ref<TagCategory>
Category: '' as Ref<TagCategory>,
OtherLabel: '' as Ref<TagCategory>
},
component: {
VacancyItemPresenter: '' as AnyComponent,

View File

@ -13,21 +13,23 @@
// limitations under the License.
-->
<script lang="ts">
import core, { AnyAttribute, Class, Doc, Ref } from '@anticrm/core'
import core, { AnyAttribute, Class, Doc, Ref, RefTo, Type } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import presentation, { getClient, MessageBox } from '@anticrm/presentation'
import {
Action,
CircleButton,
Component,
eventToHTMLElement,
IconAdd,
IconDelete,
IconEdit,
IconMoreV,
Label,
Menu,
IconMoreV,
showPopup
} from '@anticrm/ui'
import BooleanPresenter from '@anticrm/view-resources/src/components/BooleanPresenter.svelte'
import view from '@anticrm/view'
import setting from '../plugin'
import CreateAttribute from './CreateAttribute.svelte'
import EditAttribute from './EditAttribute.svelte'
@ -74,7 +76,7 @@
}
async function showMenu (ev: MouseEvent, attribute: AnyAttribute) {
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exist: true } })) !== undefined
const exist = (await client.findOne(attribute.attributeOf, { [attribute.name]: { $exists: true } })) !== undefined
const actions: Action[] = [
{
@ -94,6 +96,9 @@
]
showPopup(Menu, { actions }, eventToHTMLElement(ev), () => {})
}
function getRefClassTo (value: Type<Type<any>>): IntlString {
return client.getHierarchy().getClass((value as RefTo<Doc>).to).label
}
</script>
<div class="flex-between trans-title mb-3">
@ -122,6 +127,7 @@
</thead>
<tbody>
{#each attributes as attr}
{@const attrType = attr.type._class === core.class.RefTo ? getRefClassTo(attr.type) : undefined}
<tr class="antiTable-body__row" on:contextmenu|preventDefault={(ev) => showMenu(ev, attr)}>
<td>
<div class="antiTable-cells__firstCell">
@ -135,9 +141,12 @@
</td>
<td>
<Label label={attr.type.label} />
{#if attrType !== undefined}
: <Label label={attrType} />
{/if}
</td>
<td>
<BooleanPresenter value={attr.isCustom ?? false} />
<Component is={view.component.BooleanTruePresenter} props={{ value: attr.isCustom ?? false }} />
</td>
</tr>
{/each}

View File

@ -30,7 +30,7 @@
$: query.query(
tags.class.TagReference,
{ attachedTo: object._id, attachedToClass: _class },
{ attachedTo: object._id, attachedToClass: object._class },
(result) => {
items = result
},
@ -38,7 +38,7 @@
)
async function addRef (tag: TagElement): Promise<void> {
await client.addCollection(tags.class.TagReference, object.space, object._id, _class, key.key, {
await client.addCollection(tags.class.TagReference, object.space, object._id, object._class, key.key, {
title: tag.title,
tag: tag._id,
color: tag.color
@ -46,7 +46,7 @@
}
async function removeTag (id: Ref<TagReference>): Promise<void> {
await client.removeCollection(tags.class.TagReference, object.space, id, object._id, _class, key.key)
await client.removeCollection(tags.class.TagReference, object.space, id, object._id, object._class, key.key)
}
let elements: Map<Ref<TagElement>, TagElement> = new Map()
@ -60,7 +60,7 @@
bind:elements
{key}
bind:items
targetClass={_class}
targetClass={object._class}
on:open={(evt) => addRef(evt.detail)}
on:delete={(evt) => removeTag(evt.detail)}
/>

View File

@ -49,7 +49,7 @@
targetClass,
selected: items.map((it) => it.tag),
keyLabel,
hideAdd: true
hideAdd: false
},
evt.target as HTMLElement,
() => {},

View File

@ -138,7 +138,11 @@
{#each categories as cat}
{#if objects.filter((el) => el.category === cat._id).length > 0}
<div class="sticky-wrapper">
<button class="menu-group__header" class:show={search !== '' || show} on:click={toggleGroup}>
<button
class="menu-group__header"
class:show={categories.length === 1 || search !== '' || show}
on:click={toggleGroup}
>
<div class="flex-row-center">
<span class="mr-1-5">{cat.label}</span>
<div class="icon">

View File

@ -1,8 +1,9 @@
import { Doc, Hierarchy } from '@anticrm/core'
import { getResource, Resource } from '@anticrm/platform'
import { getClient, MessageBox } from '@anticrm/presentation'
import { getClient, MessageBox, updateAttribute } from '@anticrm/presentation'
import {
AnyComponent,
AnySvelteComponent,
closeTooltip,
isPopupPosAlignment,
PopupAlignment,
@ -191,6 +192,65 @@ async function ShowPopup (
showPopup(props.component, cprops, element)
}
/**
* Quick action for show popup
* Props:
* - attribute - to show editor for specific attribute
* - props - some basic props, will be merged with key, _class, value, values
*/
async function ShowEditor (
doc: Doc | Doc[],
evt: Event,
props: {
element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined>
attribute: string
props?: Record<string, any>
}
): Promise<void> {
const docs = Array.isArray(doc) ? doc : doc !== undefined ? [doc] : []
evt.preventDefault()
let cprops = {
...(props?.props ?? {})
}
if (docs.length === 1) {
const client = getClient()
const hierarchy = client.getHierarchy()
const doc: Doc = docs[0]
const attribute = hierarchy.getAttribute(doc._class, props.attribute)
const typeClass = hierarchy.getClass(attribute.type._class)
const attributeEditorMixin = hierarchy.as(typeClass, view.mixin.AttributeEditor)
if (attributeEditorMixin === undefined || attributeEditorMixin.popup === undefined) {
throw new Error(`failed to find editor popup for ${typeClass._id}`)
}
const editor: AnySvelteComponent = await getResource(attributeEditorMixin.popup)
cprops = {
...cprops,
...{
value: (doc as any)[props.attribute]
}
}
if (editor !== undefined) {
console.log('EVT', evt)
showPopup(
editor,
cprops,
{
getBoundingClientRect: () => new DOMRect((evt as MouseEvent).clientX, (evt as MouseEvent).clientY)
},
(result) => {
if (result != null) {
void updateAttribute(client, doc, doc._class, { key: props.attribute, attr: attribute }, result)
}
}
)
}
}
}
function UpdateDocument (doc: Doc | Doc[], evt: Event, props: Record<string, any>): void {
async function update (): Promise<void> {
if (props?.key !== undefined && props?.value !== undefined) {
@ -258,5 +318,6 @@ export const actionImpl = {
Open,
UpdateDocument,
ShowPanel,
ShowPopup
ShowPopup,
ShowEditor
}

View File

@ -0,0 +1,36 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 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 { getPlatformColor } from '@anticrm/ui'
export let value: boolean
$: color = value ? getPlatformColor(0) : getPlatformColor(11)
</script>
{#if value}
<div class="flex-center">
<div class="pinned-container" style="background-color: {color};" />
</div>
{/if}
<style lang="scss">
.pinned-container {
width: 0.5rem;
height: 0.5rem;
border-radius: 0.5rem;
}
</style>

View File

@ -45,6 +45,8 @@ import ValueFilter from './components/filter/ValueFilter.svelte'
import ObjectFilter from './components/filter/ObjectFilter.svelte'
import TimestampFilter from './components/filter/TimestampFilter.svelte'
import ClassPresenter from './components/ClassPresenter.svelte'
import EditBoxPopup from './components/EditBoxPopup.svelte'
import BooleanTruePresenter from './components/BooleanTruePresenter.svelte'
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
return getEventPopupPositionElement(e)
@ -99,7 +101,9 @@ export default async (): Promise<Resources> => ({
IntlStringPresenter,
GithubPresenter,
YoutubePresenter,
ActionsPopup
ActionsPopup,
StringEditorPopup: EditBoxPopup,
BooleanTruePresenter
},
popup: {
PositionElementAlignment

View File

@ -86,6 +86,8 @@ export interface AttributeFilter extends Class<Type<any>> {
*/
export interface AttributeEditor extends Class<Doc> {
editor: AnyComponent
// If defined could be used for ShowEditor declarative actions.
popup?: AnyComponent
}
/**
@ -390,7 +392,8 @@ const view = plugin(viewId, {
ObjectPresenter: '' as AnyComponent,
EditDoc: '' as AnyComponent,
ViewletSetting: '' as AnyComponent,
SpacePresenter: '' as AnyComponent
SpacePresenter: '' as AnyComponent,
BooleanTruePresenter: '' as AnyComponent
},
string: {
CustomizeView: '' as IntlString
@ -440,6 +443,11 @@ const view = plugin(viewId, {
value?: string
values?: string
props?: Record<string, any>
}>,
ShowEditor: '' as ViewAction<{
element?: PopupPosAlignment | Resource<(e?: Event) => PopupAlignment | undefined>
attribute: string
props?: Record<string, any>
}>
}
})