Merge branch 'main' of github.com:hcengineering/anticrm into show-filter-items-current-space

Signed-off-by: Denis Bunakalya <denis.bunakalya@xored.com>
This commit is contained in:
Denis Bunakalya
2023-01-18 11:53:37 +04:00
parent 328a052d8f
commit 49bf38cf0a
221 changed files with 3593 additions and 2272 deletions
+2 -1
View File
@@ -77,4 +77,5 @@ dist_cache
tsconfig.tsbuildinfo
ingest-attachment-*.zip
tsdoc-metadata.json
pods/front/dist
pods/front/dist
*.cpuprofile
+3 -1
View File
@@ -27,6 +27,7 @@ import { settingOperation } from '@hcengineering/model-setting'
import { recruitOperation } from '@hcengineering/model-recruit'
import { tagsOperation } from '@hcengineering/model-tags'
import { taskOperation } from '@hcengineering/model-task'
import { inventoryOperation } from '@hcengineering/model-inventory'
import { telegramOperation } from '@hcengineering/model-telegram'
import { templatesOperation } from '@hcengineering/model-templates'
import { viewOperation } from '@hcengineering/model-view'
@@ -58,5 +59,6 @@ export const migrateOperations: MigrateOperation[] = [
boardOperation,
hrOperation,
documentOperation,
bitrixOperation
bitrixOperation,
inventoryOperation
]
+1 -1
View File
@@ -77,7 +77,7 @@ export class TSavedAttachments extends TPreference implements SavedAttachments {
export function createModel (builder: Builder): void {
builder.createModel(TAttachment, TPhoto, TSavedAttachments)
builder.mixin(attachment.class.Attachment, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(attachment.class.Attachment, core.class.Class, view.mixin.ObjectPresenter, {
presenter: attachment.component.AttachmentPresenter
})
+3 -3
View File
@@ -212,15 +212,15 @@ export function createModel (builder: Builder): void {
editor: board.component.EditCard
})
builder.mixin(board.class.Card, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(board.class.Card, core.class.Class, view.mixin.ObjectPresenter, {
presenter: board.component.CardPresenter
})
builder.mixin(board.class.Board, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(board.class.Board, core.class.Class, view.mixin.ObjectPresenter, {
presenter: board.component.BoardPresenter
})
builder.mixin(board.class.CardCover, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(board.class.CardCover, core.class.Class, view.mixin.ObjectPresenter, {
presenter: board.component.CardCoverPresenter
})
+2 -2
View File
@@ -187,7 +187,7 @@ export function createModel (builder: Builder): void {
calendar.action.SaveEventReminder
)
builder.mixin(calendar.mixin.Reminder, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(calendar.mixin.Reminder, core.class.Class, view.mixin.ObjectPresenter, {
presenter: calendar.component.ReminderPresenter
})
@@ -195,7 +195,7 @@ export function createModel (builder: Builder): void {
editor: calendar.component.EditEvent
})
builder.mixin(calendar.class.Event, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(calendar.class.Event, core.class.Class, view.mixin.ObjectPresenter, {
presenter: calendar.component.EventPresenter
})
}
+3 -3
View File
@@ -190,11 +190,11 @@ export function createModel (builder: Builder, options = { addApplication: true
getName: chunter.function.GetDmName
})
builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(chunter.class.DirectMessage, core.class.Class, view.mixin.ObjectPresenter, {
presenter: chunter.component.DmPresenter
})
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ObjectPresenter, {
presenter: chunter.component.ChannelPresenter
})
@@ -404,7 +404,7 @@ export function createModel (builder: Builder, options = { addApplication: true
)
}
builder.mixin(chunter.class.Comment, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(chunter.class.Comment, core.class.Class, view.mixin.ObjectPresenter, {
presenter: chunter.component.CommentPresenter
})
+27 -11
View File
@@ -48,7 +48,7 @@ import attachment from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter'
import core, { TAccount, TAttachedDoc, TDoc, TSpace } from '@hcengineering/model-core'
import presentation from '@hcengineering/model-presentation'
import view, { actionTemplates, createAction, ViewAction } from '@hcengineering/model-view'
import view, { actionTemplates, createAction, ViewAction, Viewlet } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import type { Asset, IntlString, Resource } from '@hcengineering/platform'
import setting from '@hcengineering/setting'
@@ -204,7 +204,7 @@ export function createModel (builder: Builder): void {
contact.app.Contacts
)
builder.createDoc(
builder.createDoc<Viewlet>(
view.class.Viewlet,
core.space.Model,
{
@@ -229,7 +229,7 @@ export function createModel (builder: Builder): void {
pinned: true
})
builder.createDoc(
builder.createDoc<Viewlet>(
view.class.Viewlet,
core.space.Model,
{
@@ -237,7 +237,7 @@ export function createModel (builder: Builder): void {
descriptor: view.viewlet.Table,
config: [
'',
'$lookup._class',
'_class',
'city',
'attachments',
'modifiedOn',
@@ -280,7 +280,7 @@ export function createModel (builder: Builder): void {
inlineEditor: contact.component.EmployeeArrayEditor
})
builder.mixin(contact.class.Member, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(contact.class.Member, core.class.Class, view.mixin.ObjectPresenter, {
presenter: contact.component.MemberPresenter
})
@@ -292,7 +292,7 @@ export function createModel (builder: Builder): void {
inlineEditor: contact.component.EmployeeEditor
})
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(contact.class.Channel, core.class.Class, view.mixin.ObjectPresenter, {
presenter: contact.component.ChannelsPresenter
})
@@ -401,7 +401,7 @@ export function createModel (builder: Builder): void {
contact.avatarProvider.Gravatar
)
builder.mixin(contact.class.Person, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(contact.class.Person, core.class.Class, view.mixin.ObjectPresenter, {
presenter: contact.component.PersonPresenter
})
@@ -413,22 +413,38 @@ export function createModel (builder: Builder): void {
inlineEditor: contact.component.AccountArrayEditor
})
builder.mixin(core.class.Account, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(core.class.Account, core.class.Class, view.mixin.ObjectPresenter, {
presenter: contact.component.EmployeeAccountPresenter
})
builder.mixin(contact.class.Organization, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(contact.class.Organization, core.class.Class, view.mixin.ObjectPresenter, {
presenter: contact.component.OrganizationPresenter
})
builder.mixin(contact.class.Contact, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(contact.class.Contact, core.class.Class, view.mixin.ObjectPresenter, {
presenter: contact.component.ContactPresenter
})
builder.mixin(contact.class.Employee, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(contact.class.Employee, core.class.Class, view.mixin.ObjectPresenter, {
presenter: contact.component.EmployeePresenter
})
builder.mixin(contact.class.Employee, core.class.Class, view.mixin.SortFuncs, {
func: contact.function.EmployeeSort
})
builder.mixin(contact.class.Person, core.class.Class, view.mixin.AttributePresenter, {
presenter: contact.component.PersonRefPresenter
})
builder.mixin(contact.class.Contact, core.class.Class, view.mixin.AttributePresenter, {
presenter: contact.component.ContactRefPresenter
})
builder.mixin(contact.class.Employee, core.class.Class, view.mixin.AttributePresenter, {
presenter: contact.component.EmployeeRefPresenter
})
builder.mixin(contact.class.Employee, core.class.Class, view.mixin.IgnoreActions, {
actions: [view.action.Delete]
})
+3
View File
@@ -25,6 +25,7 @@ import { Action, ActionCategory, ViewAction } from '@hcengineering/view'
export default mergeIds(contactId, contact, {
component: {
PersonPresenter: '' as AnyComponent,
ContactRefPresenter: '' as AnyComponent,
ContactPresenter: '' as AnyComponent,
EditPerson: '' as AnyComponent,
EditOrganization: '' as AnyComponent,
@@ -35,6 +36,8 @@ export default mergeIds(contactId, contact, {
EmployeeAccountPresenter: '' as AnyComponent,
OrganizationEditor: '' as AnyComponent,
EmployeePresenter: '' as AnyComponent,
EmployeeRefPresenter: '' as AnyComponent,
PersonRefPresenter: '' as AnyComponent,
PersonEditor: '' as AnyComponent,
Members: '' as AnyComponent,
MemberPresenter: '' as AnyComponent,
+2 -2
View File
@@ -224,10 +224,10 @@ export function createModel (builder: Builder): void {
editor: document.component.EditDoc
})
builder.mixin(document.class.Document, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(document.class.Document, core.class.Class, view.mixin.ObjectPresenter, {
presenter: document.component.DocumentPresenter
})
builder.mixin(document.class.DocumentVersion, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(document.class.DocumentVersion, core.class.Class, view.mixin.ObjectPresenter, {
presenter: document.component.DocumentVersionPresenter
})
+1 -1
View File
@@ -387,7 +387,7 @@ export function createModel (builder: Builder): void {
}
})
builder.mixin(hr.class.Request, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(hr.class.Request, core.class.Class, view.mixin.ObjectPresenter, {
presenter: hr.component.RequestPresenter
})
}
+11 -7
View File
@@ -22,8 +22,7 @@ import { createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification'
import setting from '@hcengineering/setting'
import type {} from '@hcengineering/view'
import view from '@hcengineering/view'
import view, { Viewlet } from '@hcengineering/view'
import inventory from './plugin'
export const DOMAIN_INVENTORY = 'inventory' as Domain
@@ -75,15 +74,19 @@ export class TVariant extends TAttachedDoc implements Variant {
export function createModel (builder: Builder): void {
builder.createModel(TCategory, TProduct, TVariant)
builder.mixin(inventory.class.Category, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(inventory.class.Category, core.class.Class, view.mixin.ObjectPresenter, {
presenter: inventory.component.CategoryPresenter
})
builder.mixin(inventory.class.Product, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(inventory.class.Category, core.class.Class, view.mixin.AttributePresenter, {
presenter: inventory.component.CategoryRefPresenter
})
builder.mixin(inventory.class.Product, core.class.Class, view.mixin.ObjectPresenter, {
presenter: inventory.component.ProductPresenter
})
builder.mixin(inventory.class.Variant, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(inventory.class.Variant, core.class.Class, view.mixin.ObjectPresenter, {
presenter: inventory.component.VariantPresenter
})
@@ -99,13 +102,13 @@ export function createModel (builder: Builder): void {
value: true
})
builder.createDoc(
builder.createDoc<Viewlet>(
view.class.Viewlet,
core.space.Model,
{
attachTo: inventory.class.Product,
descriptor: view.viewlet.Table,
config: ['', '$lookup.attachedTo', 'modifiedOn']
config: ['', 'attachedTo', 'modifiedOn']
},
inventory.viewlet.TableProduct
)
@@ -173,4 +176,5 @@ export function createModel (builder: Builder): void {
})
}
export { inventoryOperation } from './migration'
export { default } from './plugin'
+21
View File
@@ -0,0 +1,21 @@
//
// 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.
//
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
export const inventoryOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
}
+1
View File
@@ -36,6 +36,7 @@ export default mergeIds(inventoryId, inventory, {
CreateProduct: '' as AnyComponent,
EditProduct: '' as AnyComponent,
CategoryPresenter: '' as AnyComponent,
CategoryRefPresenter: '' as AnyComponent,
Variants: '' as AnyComponent,
ProductPresenter: '' as AnyComponent,
VariantPresenter: '' as AnyComponent
+35 -5
View File
@@ -189,7 +189,7 @@ export function createModel (builder: Builder): void {
descriptor: view.viewlet.Table,
config: [
'',
'$lookup._class',
'_class',
'leads',
'modifiedOn',
{
@@ -212,9 +212,9 @@ export function createModel (builder: Builder): void {
config: [
'',
'title',
'$lookup.attachedTo',
'$lookup.state',
'$lookup.doneState',
'attachedTo',
'state',
'doneState',
'attachments',
'comments',
'modifiedOn',
@@ -227,6 +227,36 @@ export function createModel (builder: Builder): void {
lead.viewlet.TableLead
)
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: lead.class.Lead,
descriptor: view.viewlet.List,
config: [
{ key: '', props: { fixed: 'left' } },
{ key: 'title', props: { fixed: 'left' } },
{ key: 'state', props: { fixed: 'left' } },
{ key: 'doneState', props: { fixed: 'left' } },
{ key: '', presenter: view.component.GrowPresenter },
'attachments',
'comments',
'assignee'
],
viewOptions: {
groupBy: ['assignee', 'state', 'attachedTo'],
orderBy: [
['assignee', -1],
['state', 1],
['attachedTo', 1],
['modifiedOn', -1]
],
other: []
}
},
lead.viewlet.ListLead
)
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: lead.class.Lead,
descriptor: task.viewlet.Kanban,
@@ -254,7 +284,7 @@ export function createModel (builder: Builder): void {
editor: lead.component.EditLead
})
builder.mixin(lead.class.Lead, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(lead.class.Lead, core.class.Class, view.mixin.ObjectPresenter, {
presenter: lead.component.LeadPresenter
})
+2 -2
View File
@@ -30,7 +30,6 @@ export default mergeIds(leadId, lead, {
LeadApplication: '' as IntlString,
Lead: '' as IntlString,
Title: '' as IntlString,
Assignee: '' as IntlString,
ManageFunnelStatuses: '' as IntlString,
FunnelBrowser: '' as IntlString,
GotoLeadApplication: '' as IntlString
@@ -52,7 +51,8 @@ export default mergeIds(leadId, lead, {
},
viewlet: {
TableCustomer: '' as Ref<Viewlet>,
TableLead: '' as Ref<Viewlet>
TableLead: '' as Ref<Viewlet>,
ListLead: '' as Ref<Viewlet>
},
category: {
Lead: '' as Ref<ActionCategory>
+6 -6
View File
@@ -351,10 +351,10 @@ export function createModel (builder: Builder): void {
descriptor: task.viewlet.StatusTable,
config: [
'',
'$lookup.attachedTo',
'$lookup.assignee',
'$lookup.state',
'$lookup.doneState',
'attachedTo',
'assignee',
'state',
'doneState',
'attachments',
'comments',
'modifiedOn',
@@ -415,7 +415,7 @@ export function createModel (builder: Builder): void {
editor: recruit.component.EditVacancy
})
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ObjectPresenter, {
presenter: recruit.component.ApplicationPresenter
})
@@ -423,7 +423,7 @@ export function createModel (builder: Builder): void {
presenter: recruit.component.ApplicationsPresenter
})
builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(recruit.class.Vacancy, core.class.Class, view.mixin.ObjectPresenter, {
presenter: recruit.component.VacancyPresenter
})
+2 -2
View File
@@ -60,11 +60,11 @@ export function createReviewModel (builder: Builder): void {
editor: recruit.component.EditReview
})
builder.mixin(recruit.class.Review, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(recruit.class.Review, core.class.Class, view.mixin.ObjectPresenter, {
presenter: recruit.component.ReviewPresenter
})
builder.mixin(recruit.class.Opinion, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(recruit.class.Opinion, core.class.Class, view.mixin.ObjectPresenter, {
presenter: recruit.component.OpinionPresenter
})
+1 -1
View File
@@ -56,7 +56,7 @@ export function createModel (builder: Builder): void {
editor: request.component.EditRequest
})
builder.mixin(request.class.Request, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(request.class.Request, core.class.Class, view.mixin.ObjectPresenter, {
presenter: request.component.RequestPresenter
})
+2 -2
View File
@@ -106,10 +106,10 @@ export function createModel (builder: Builder): void {
inlineEditor: tags.component.TagsAttributeEditor
})
builder.mixin(tags.class.TagReference, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tags.class.TagReference, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tags.component.TagReferencePresenter
})
builder.mixin(tags.class.TagElement, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tags.class.TagElement, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tags.component.TagElementPresenter
})
+15 -16
View File
@@ -366,29 +366,20 @@ export function createModel (builder: Builder): void {
{
attachTo: task.class.Issue,
descriptor: task.viewlet.StatusTable,
config: [
'',
'name',
'$lookup.assignee',
'$lookup.state',
'$lookup.doneState',
'attachments',
'comments',
'modifiedOn'
]
config: ['', 'name', 'assignee', 'state', 'doneState', 'attachments', 'comments', 'modifiedOn']
},
task.viewlet.TableIssue
)
builder.mixin(task.class.Task, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(task.class.Task, core.class.Class, view.mixin.ObjectPresenter, {
presenter: view.component.ObjectPresenter
})
builder.mixin(task.class.Issue, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(task.class.Issue, core.class.Class, view.mixin.ObjectPresenter, {
presenter: task.component.TaskPresenter
})
builder.mixin(task.class.KanbanTemplate, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(task.class.KanbanTemplate, core.class.Class, view.mixin.ObjectPresenter, {
presenter: task.component.KanbanTemplatePresenter
})
@@ -462,10 +453,14 @@ export function createModel (builder: Builder): void {
inlineEditor: task.component.StateEditor
})
builder.mixin(task.class.State, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(task.class.State, core.class.Class, view.mixin.ObjectPresenter, {
presenter: task.component.StatePresenter
})
builder.mixin(task.class.State, core.class.Class, view.mixin.AttributePresenter, {
presenter: task.component.StateRefPresenter
})
builder.mixin(task.class.State, core.class.Class, view.mixin.IgnoreActions, {
actions: [view.action.Delete]
})
@@ -474,10 +469,14 @@ export function createModel (builder: Builder): void {
inlineEditor: task.component.DoneStateEditor
})
builder.mixin(task.class.DoneState, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(task.class.DoneState, core.class.Class, view.mixin.ObjectPresenter, {
presenter: task.component.DoneStatePresenter
})
builder.mixin(task.class.DoneState, core.class.Class, view.mixin.AttributePresenter, {
presenter: task.component.DoneStateRefPresenter
})
builder.createDoc(
view.class.ViewletDescriptor,
core.space.Model,
@@ -504,7 +503,7 @@ export function createModel (builder: Builder): void {
editor: task.component.Todos
})
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.ObjectPresenter, {
presenter: task.component.TodoItemPresenter
})
+3 -1
View File
@@ -58,7 +58,9 @@ export default mergeIds(taskId, task, {
TodoItemPresenter: '' as AnyComponent,
StatusTableView: '' as AnyComponent,
TaskHeader: '' as AnyComponent,
Dashboard: '' as AnyComponent
Dashboard: '' as AnyComponent,
StateRefPresenter: '' as AnyComponent,
DoneStateRefPresenter: '' as AnyComponent
},
space: {
TasksPublic: '' as Ref<Space>
+143 -30
View File
@@ -73,7 +73,7 @@ import {
trackerId,
WorkDayLength
} from '@hcengineering/tracker'
import { KeyBinding } from '@hcengineering/view'
import { KeyBinding, ViewOptionsModel } from '@hcengineering/view'
import tracker from './plugin'
import presentation from '@hcengineering/model-presentation'
@@ -467,9 +467,31 @@ export function createModel (builder: Builder): void {
TTypeReportedTime
)
const issuesOptions: ViewOptionsModel = {
groupBy: ['status', 'assignee', 'priority', 'project', 'sprint'],
orderBy: [
['status', SortingOrder.Ascending],
['priority', SortingOrder.Ascending],
['modifiedOn', SortingOrder.Descending],
['dueDate', SortingOrder.Descending],
['rank', SortingOrder.Ascending]
],
other: [
{
key: 'shouldShowSubIssues',
type: 'toggle',
defaultValue: false,
actionTartget: 'query',
action: tracker.function.SubIssueQuery,
label: tracker.string.SubIssues
}
]
}
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: tracker.class.Issue,
descriptor: tracker.viewlet.List,
descriptor: view.viewlet.List,
viewOptions: issuesOptions,
config: [
{
key: '',
@@ -484,7 +506,7 @@ export function createModel (builder: Builder): void {
},
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true } },
{ key: '', presenter: tracker.component.SubIssuesSelector, props: {} },
{ key: '', presenter: tracker.component.GrowPresenter, props: { type: 'grow' } },
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
{ key: '', presenter: tracker.component.DueDatePresenter, props: { kind: 'list' } },
{
key: '',
@@ -528,13 +550,91 @@ export function createModel (builder: Builder): void {
]
})
const subIssuesOptions: ViewOptionsModel = {
groupBy: ['status', 'assignee', 'priority', 'sprint'],
orderBy: [
['status', SortingOrder.Ascending],
['priority', SortingOrder.Ascending],
['modifiedOn', SortingOrder.Descending],
['dueDate', SortingOrder.Descending],
['rank', SortingOrder.Ascending]
],
other: []
}
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: tracker.class.Issue,
descriptor: view.viewlet.List,
viewOptions: subIssuesOptions,
variant: 'subissue',
config: [
{
key: '',
presenter: tracker.component.PriorityEditor,
props: { type: 'priority', kind: 'list', size: 'small' }
},
{ key: '', presenter: tracker.component.IssuePresenter, props: { type: 'issue', fixed: 'left' } },
{
key: '',
presenter: tracker.component.StatusEditor,
props: { kind: 'list', size: 'small', justify: 'center' }
},
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true, showParent: false } },
{ key: '', presenter: tracker.component.SubIssuesSelector, props: {} },
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
{ key: '', presenter: tracker.component.DueDatePresenter, props: { kind: 'list' } },
{
key: '',
presenter: tracker.component.SprintEditor,
props: {
kind: 'list',
size: 'small',
shape: 'round',
shouldShowPlaceholder: false,
excludeByKey: 'sprint',
optional: true
}
},
{
key: '',
presenter: tracker.component.EstimationEditor,
props: { kind: 'list', size: 'small', optional: true }
},
{
key: 'modifiedOn',
presenter: tracker.component.ModificationDatePresenter,
props: { fixed: 'right', optional: true }
},
{
key: '$lookup.assignee',
presenter: tracker.component.AssigneePresenter,
props: { issueClass: tracker.class.Issue, defaultClass: contact.class.Employee, shouldShowLabel: false }
}
]
},
tracker.viewlet.SubIssues
)
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: tracker.class.IssueTemplate,
descriptor: tracker.viewlet.List,
descriptor: view.viewlet.List,
viewOptions: {
groupBy: ['assignee', 'priority', 'project', 'sprint'],
orderBy: [
['priority', SortingOrder.Ascending],
['modifiedOn', SortingOrder.Descending],
['dueDate', SortingOrder.Descending],
['rank', SortingOrder.Ascending]
],
other: []
},
config: [
// { key: '', presenter: tracker.component.PriorityEditor, props: { kind: 'list', size: 'small' } },
{ key: '', presenter: tracker.component.IssueTemplatePresenter, props: { type: 'issue', shouldUseMargin: true } },
{ key: '', presenter: tracker.component.GrowPresenter, props: { type: 'grow' } },
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
// { key: '', presenter: tracker.component.DueDatePresenter, props: { kind: 'list' } },
{
key: '',
@@ -556,20 +656,10 @@ export function createModel (builder: Builder): void {
]
})
builder.createDoc(
view.class.ViewletDescriptor,
core.space.Model,
{
label: tracker.string.List,
icon: view.icon.Table,
component: tracker.component.ListView
},
tracker.viewlet.List
)
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: tracker.class.Issue,
descriptor: tracker.viewlet.Kanban,
viewOptions: issuesOptions,
config: []
})
@@ -657,11 +747,11 @@ export function createModel (builder: Builder): void {
const sprintsId = 'sprints'
const templatesId = 'templates'
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tracker.component.IssuePresenter
})
builder.mixin(tracker.class.IssueTemplate, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tracker.class.IssueTemplate, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tracker.component.IssueTemplatePresenter
})
@@ -669,7 +759,7 @@ export function createModel (builder: Builder): void {
presenter: tracker.component.IssuePreview
})
builder.mixin(tracker.class.TimeSpendReport, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tracker.class.TimeSpendReport, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tracker.component.TimeSpendReport
})
@@ -677,7 +767,23 @@ export function createModel (builder: Builder): void {
titleProvider: tracker.function.IssueTitleProvider
})
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ListHeaderExtra, {
presenters: [tracker.component.IssueStatistics]
})
builder.mixin(tracker.class.IssueStatus, core.class.Class, view.mixin.SortFuncs, {
func: tracker.function.IssueStatusSort
})
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.SortFuncs, {
func: tracker.function.IssuePrioritySort
})
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.SortFuncs, {
func: tracker.function.SprintSort
})
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tracker.component.PriorityPresenter
})
@@ -685,33 +791,40 @@ export function createModel (builder: Builder): void {
component: view.component.ValueFilter
})
builder.mixin(tracker.class.IssueStatus, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tracker.class.IssueStatus, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tracker.component.StatusPresenter
})
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tracker.class.IssueStatus, core.class.Class, view.mixin.AttributePresenter, {
presenter: tracker.component.StatusRefPresenter
})
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AttributePresenter, {
presenter: tracker.component.PriorityRefPresenter
})
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tracker.component.ProjectTitlePresenter
})
builder.mixin(tracker.class.Team, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tracker.class.Team, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tracker.component.TeamPresenter
})
classPresenter(
builder,
tracker.class.Project,
tracker.component.ProjectTitlePresenter,
tracker.component.ProjectSelector
)
classPresenter(builder, tracker.class.Project, tracker.component.ProjectSelector, tracker.component.ProjectSelector)
builder.mixin(tracker.class.Project, core.class.Class, view.mixin.AttributeEditor, {
inlineEditor: tracker.component.ProjectSelector
})
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.ObjectPresenter, {
presenter: tracker.component.SprintTitlePresenter
})
builder.mixin(tracker.class.Sprint, core.class.Class, view.mixin.AttributePresenter, {
presenter: tracker.component.SprintRefPresenter
})
builder.mixin(tracker.class.Issue, core.class.Class, setting.mixin.Editable, {
value: true
})
+2 -1
View File
@@ -41,7 +41,8 @@ export default mergeIds(trackerId, tracker, {
// Required to pass build without errorsF
Nope: '' as AnyComponent,
SprintSelector: '' as AnyComponent,
SubIssuesSelector: '' as AnyComponent
SubIssuesSelector: '' as AnyComponent,
IssueStatistics: '' as AnyComponent
},
app: {
Tracker: '' as Ref<Application>
+1
View File
@@ -32,6 +32,7 @@
"@hcengineering/view": "^0.6.2",
"@hcengineering/view-resources": "^0.6.0",
"@hcengineering/model-core": "^0.6.0",
"@hcengineering/preference": "^0.6.2",
"@hcengineering/model-preference": "^0.6.0"
}
}
+46 -4
View File
@@ -29,6 +29,7 @@ import type {
AttributePresenter,
BuildModelKey,
ClassFilters,
ClassSortFuncs,
CollectionEditor,
CollectionPresenter,
Filter,
@@ -38,13 +39,16 @@ import type {
KeyBinding,
KeyFilter,
LinkPresenter,
ListHeaderExtra,
ListItemPresenter,
ObjectEditor,
ObjectEditorHeader,
ObjectFactory,
ObjectPresenter,
ObjectTitle,
ObjectValidator,
PreviewPresenter,
ListItemPresenter,
SortFunc,
SpaceHeader,
SpaceName,
ViewAction,
@@ -52,7 +56,8 @@ import type {
ViewContext,
Viewlet,
ViewletDescriptor,
ViewletPreference
ViewletPreference,
ViewOptionsModel
} from '@hcengineering/view'
import view from './plugin'
@@ -134,6 +139,11 @@ export class TAttributePresenter extends TClass implements AttributePresenter {
presenter!: AnyComponent
}
@Mixin(view.mixin.ObjectPresenter, core.class.Class)
export class TObjectPresenter extends TClass implements ObjectPresenter {
presenter!: AnyComponent
}
@Mixin(view.mixin.ListItemPresenter, core.class.Class)
export class TListItemPresenter extends TClass implements ListItemPresenter {
presenter!: AnyComponent
@@ -176,6 +186,16 @@ export class TObjectTitle extends TClass implements ObjectTitle {
titleProvider!: Resource<<T extends Doc>(client: Client, ref: Ref<T>) => Promise<string>>
}
@Mixin(view.mixin.ListHeaderExtra, core.class.Class)
export class TListHeaderExtra extends TClass implements ListHeaderExtra {
presenters!: AnyComponent[]
}
@Mixin(view.mixin.SortFuncs, core.class.Class)
export class TSortFuncs extends TClass implements ClassSortFuncs {
func!: SortFunc
}
@Model(view.class.ViewletPreference, preference.class.Preference)
export class TViewletPreference extends TPreference implements ViewletPreference {
attachedTo!: Ref<Viewlet>
@@ -190,11 +210,12 @@ export class TViewletDescriptor extends TDoc implements ViewletDescriptor {
@Model(view.class.Viewlet, core.class.Doc, DOMAIN_MODEL)
export class TViewlet extends TDoc implements Viewlet {
attachTo!: Ref<Class<Space>>
attachTo!: Ref<Class<Doc>>
descriptor!: Ref<ViewletDescriptor>
open!: AnyComponent
config!: (BuildModelKey | string)[]
hiddenKeys?: string[]
viewOptions?: ViewOptionsModel
}
@Model(view.class.Action, core.class.Doc, DOMAIN_MODEL)
@@ -279,6 +300,9 @@ export function createModel (builder: Builder): void {
TCollectionEditor,
TCollectionPresenter,
TObjectEditor,
TObjectPresenter,
TSortFuncs,
TListHeaderExtra,
TViewletPreference,
TViewletDescriptor,
TViewlet,
@@ -330,7 +354,14 @@ export function createModel (builder: Builder): void {
classPresenter(builder, core.class.TypeTimestamp, view.component.TimestampPresenter)
classPresenter(builder, core.class.TypeDate, view.component.DatePresenter, view.component.DateEditor)
classPresenter(builder, core.class.Space, view.component.ObjectPresenter)
classPresenter(builder, core.class.Class, view.component.ClassPresenter)
classPresenter(builder, core.class.Class, view.component.ClassRefPresenter)
builder.mixin(core.class.Space, core.class.Class, view.mixin.ObjectPresenter, {
presenter: view.component.SpacePresenter
})
builder.mixin(core.class.Class, core.class.Class, view.mixin.ObjectPresenter, {
presenter: view.component.ClassPresenter
})
classPresenter(builder, core.class.TypeRelatedDocument, view.component.ObjectPresenter)
@@ -376,6 +407,17 @@ export function createModel (builder: Builder): void {
view.viewlet.Table
)
builder.createDoc(
view.class.ViewletDescriptor,
core.space.Model,
{
label: view.string.List,
icon: view.icon.List,
component: view.component.ListView
},
view.viewlet.List
)
createAction(
builder,
{
+44 -1
View File
@@ -13,9 +13,52 @@
// limitations under the License.
//
import { Ref } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import { Viewlet, ViewletPreference } from '@hcengineering/view'
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
async function migrateViewletPreference (client: MigrationClient): Promise<void> {
const targets: Record<string, string[]> = {
'inventory:viewlet:TableProduct': ['attachedTo'],
'lead:viewlet:TableCustomer': ['_class'],
'lead:viewlet:TableLead': ['attachedTo', 'state', 'doneState'],
'recruit.viewlet.TableApplicant': ['attachedTo', 'assignee', 'state', 'doneState'],
'task.viewlet.TableIssue': ['assignee', 'state', 'doneState']
}
for (const target in targets) {
const keys = targets[target]
const preferences = await client.find<ViewletPreference>(DOMAIN_PREFERENCE, {
attachedTo: target as Ref<Viewlet>
})
for (const pref of preferences) {
let needUpdate = false
for (const key of keys) {
const index = pref.config.findIndex((p) => p === `$lookup.${key}`)
if (index !== -1) {
pref.config.splice(index, 1, key)
needUpdate = true
}
}
if (needUpdate) {
await client.update<ViewletPreference>(
DOMAIN_PREFERENCE,
{
_id: pref._id
},
{
config: pref.config
}
)
}
}
}
}
export const viewOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async migrate (client: MigrationClient): Promise<void> {
await migrateViewletPreference(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
}
+5 -2
View File
@@ -58,10 +58,12 @@ export default mergeIds(viewId, view, {
YoutubePresenter: '' as AnyComponent,
GithubPresenter: '' as AnyComponent,
ClassPresenter: '' as AnyComponent,
ClassRefPresenter: '' as AnyComponent,
EnumEditor: '' as AnyComponent,
HTMLEditor: '' as AnyComponent,
MarkupEditor: '' as AnyComponent,
MarkupEditorPopup: '' as AnyComponent
MarkupEditorPopup: '' as AnyComponent,
ListView: '' as AnyComponent
},
string: {
Table: '' as IntlString,
@@ -84,7 +86,8 @@ export default mergeIds(viewId, view, {
General: '' as IntlString,
Navigation: '' as IntlString,
Editor: '' as IntlString,
MarkdownFormatting: '' as IntlString
MarkdownFormatting: '' as IntlString,
List: '' as IntlString
},
function: {
FilterObjectInResult: '' as Resource<(filter: Filter, onUpdate: () => void) => Promise<ObjQueryType<any>>>,
+1 -1
View File
@@ -48,7 +48,7 @@ export class TSpaceView extends TClass implements SpaceView {
export function createModel (builder: Builder): void {
builder.createModel(TApplication, TSpaceView, THiddenApplication)
builder.mixin(workbench.class.Application, core.class.Class, view.mixin.AttributePresenter, {
builder.mixin(workbench.class.Application, core.class.Class, view.mixin.ObjectPresenter, {
presenter: workbench.component.ApplicationPresenter
})
}
+7 -3
View File
@@ -20,6 +20,7 @@ import core from './component'
import { _createMixinProxy, _mixinClass, _toDoc } from './proxy'
import type { Tx, TxCreateDoc, TxMixin, TxRemoveDoc, TxUpdateDoc } from './tx'
import { TxProcessor } from './tx'
import getTypeOf from './typeof'
/**
* @public
@@ -463,11 +464,12 @@ export class Hierarchy {
if (typeof obj === 'function') {
return obj
}
const result: any = Array.isArray(obj) ? [] : {}
const isArray = Array.isArray(obj)
const result: any = isArray ? [] : Object.assign({}, obj)
for (const key in obj) {
// include prototype properties
const value = obj[key]
const type = {}.toString.call(value).slice(8, -1)
const type = getTypeOf(value)
if (type === 'Array') {
result[key] = this.clone(value)
} else if (type === 'Object') {
@@ -477,7 +479,9 @@ export class Hierarchy {
} else if (type === 'Date') {
result[key] = new Date(value.getTime())
} else {
result[key] = value
if (isArray) {
result[key] = value
}
}
}
return result
+35
View File
@@ -0,0 +1,35 @@
const se = typeof Symbol !== 'undefined'
const ste = se && typeof Symbol.toStringTag !== 'undefined'
export default function getTypeOf (obj: any): string {
const typeofObj = typeof obj
if (typeofObj !== 'object') {
return typeofObj
}
if (obj === null) {
return 'null'
}
if (Array.isArray(obj) && (!ste || !(Symbol.toStringTag in obj))) {
return 'Array'
}
const stringTag = ste && obj[Symbol.toStringTag]
if (typeof stringTag === 'string') {
return stringTag
}
const objPrototype = Object.getPrototypeOf(obj)
if (objPrototype === RegExp.prototype) {
return 'RegExp'
}
if (objPrototype === Date.prototype) {
return 'Date'
}
if (objPrototype === null) {
return 'Object'
}
return {}.toString.call(obj).slice(8, -1)
}
+17 -39
View File
@@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import core, { AttachedDoc, Class, Doc, DocumentQuery, DocumentUpdate, FindOptions, Ref } from '@hcengineering/core'
import { Class, Doc, DocumentQuery, DocumentUpdate, FindOptions, Ref, SortingOrder } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { getPlatformColor, ScrollBox, Scroller } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
@@ -22,14 +22,13 @@
import KanbanRow from './KanbanRow.svelte'
export let _class: Ref<Class<Item>>
export let search: string
export let options: FindOptions<Item> | undefined = undefined
export let states: TypeState[] = []
export let query: DocumentQuery<Item> = {}
export let fieldName: string
export let rankFieldName: string | undefined
export let selection: number | undefined = undefined
export let checked: Doc[] = []
export let dontUpdateRank: boolean = false
const dispatch = createEventDispatcher()
@@ -38,15 +37,13 @@
const objsQ = createQuery()
$: objsQ.query(
_class,
{
...query,
...(search !== '' ? { $search: search } : {})
},
query,
(result) => {
objects = result
dispatch('content', objects)
},
{
sort: { rank: SortingOrder.Ascending },
...options
}
)
@@ -57,10 +54,6 @@
dragItem?: Item // required for svelte to properly recalculate state.
): ExtItem[] {
const stateCards = objects.filter((it) => (it as any)[fieldName] === state._id)
if (rankFieldName !== undefined) {
const sortField = rankFieldName
stateCards.sort((a, b) => (a as any)[sortField]?.localeCompare((b as any)[sortField]))
}
return stateCards.map((it, idx, arr) => ({
it,
prev: arr[idx - 1],
@@ -69,23 +62,6 @@
}))
}
async function updateItem (item: Item, update: DocumentUpdate<Item>) {
if (client.getHierarchy().isDerived(_class, core.class.AttachedDoc)) {
const adoc: AttachedDoc = item as Doc as AttachedDoc
await client.updateCollection(
_class,
adoc.space,
adoc._id as Ref<Doc> as Ref<AttachedDoc>,
adoc.attachedTo,
adoc.attachedToClass,
adoc.collection,
update
)
} else {
await client.updateDoc(item._class, item.space, item._id, update)
}
}
async function move (state: StateType) {
if (dragCard === undefined) {
return
@@ -99,15 +75,15 @@
}
}
if (rankFieldName !== undefined && dragCardInitialRank !== (dragCard as any)[rankFieldName]) {
const dragCardRank = (dragCard as any)[rankFieldName]
if (!dontUpdateRank && dragCardInitialRank !== dragCard.rank) {
const dragCardRank = dragCard.rank
updates = {
...updates,
[rankFieldName]: dragCardRank
rank: dragCardRank
}
}
if (Object.keys(updates).length > 0) {
await updateItem(dragCard, updates)
await client.update(dragCard, updates)
}
dragCard = undefined
}
@@ -125,7 +101,7 @@
if (dragCard === undefined) {
return
}
await updateItem(dragCard, query)
await client.update(dragCard, query)
}
function doCalcRank (
object: { prev?: Item; it: Item; next?: Item },
@@ -146,26 +122,28 @@
if (card !== undefined && card[fieldName] !== state._id) {
card[fieldName] = state._id
const objs = getStateObjects(objects, state)
if (rankFieldName !== undefined) card[rankFieldName] = calcRank(objs[objs.length - 1]?.it, undefined)
if (!dontUpdateRank) {
card.rank = calcRank(objs[objs.length - 1]?.it, undefined)
}
}
}
function cardDragOver (evt: CardDragEvent, object: ExtItem): void {
if (dragCard !== undefined) {
;(dragCard as any)[fieldName] = (dragCard as any)[fieldName]
if (rankFieldName !== undefined) {
;(dragCard as any)[rankFieldName] = doCalcRank(object, evt)
if (!dontUpdateRank) {
dragCard.rank = doCalcRank(object, evt)
}
}
}
function cardDrop (evt: CardDragEvent, object: ExtItem): void {
if (dragCard !== undefined && rankFieldName !== undefined) {
;(dragCard as any)[rankFieldName] = doCalcRank(object, evt)
if (!dontUpdateRank && dragCard !== undefined) {
dragCard.rank = doCalcRank(object, evt)
}
isDragging = false
}
function onDragStart (object: ExtItem, state: TypeState): void {
dragCardInitialState = state._id
dragCardInitialRank = rankFieldName === undefined ? undefined : (object.it as any)[rankFieldName]
dragCardInitialRank = object.it.rank
dragCard = object.it
isDragging = true
dispatch('obj-focus', object.it)
+4 -2
View File
@@ -14,7 +14,7 @@
// limitations under the License.
//
import { Status, OK, unknownError } from './status'
import { Status, OK, unknownError, Severity } from './status'
/**
* @public
@@ -68,7 +68,9 @@ async function broadcastEvent (event: string, data: any): Promise<void> {
* @returns
*/
export async function setPlatformStatus (status: Status): Promise<void> {
// console.log(await translate(status.code, status.params))
if (status.severity === Severity.ERROR) {
console.trace('Platform Error Status', status)
}
return await broadcastEvent(PlatformEvent, status)
}
+2 -1
View File
@@ -27,6 +27,7 @@
"Add": "Add",
"Edit": "Edit",
"SelectAvatar": "Select avatar",
"GravatarsManaged": "Gravatars are managed through"
"GravatarsManaged": "Gravatars are managed through",
"InltPropsValue": "{value}"
}
}
+2 -1
View File
@@ -27,6 +27,7 @@
"Add": "Добавить",
"Edit": "Редактировать",
"SelectAvatar": "Выбрать аватар",
"GravatarsManaged": "Граватары управляются через"
"GravatarsManaged": "Граватары управляются через",
"InltPropsValue": "{value}"
}
}
@@ -12,11 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts" context="module">
const providers = new Map<string, AvatarProvider | null>()
async function getProvider (client: Client, providerId: Ref<AvatarProvider>): Promise<AvatarProvider | undefined> {
const p = providers.get(providerId)
if (p !== undefined) {
return p ?? undefined
}
const res = await getClient().findOne(contact.class.AvatarProvider, { _id: providerId })
providers.set(providerId, res ?? null)
return res
}
</script>
<script lang="ts">
import contact, { AvatarType, AvatarProvider } from '@hcengineering/contact'
import contact, { AvatarProvider, AvatarType } from '@hcengineering/contact'
import { Client, Ref } from '@hcengineering/core'
import { Asset, getResource } from '@hcengineering/platform'
import { AnySvelteComponent, Icon, IconSize } from '@hcengineering/ui'
import { getBlobURL, getAvatarProviderId, getClient } from '../utils'
import { getAvatarProviderId, getBlobURL, getClient } from '../utils'
import AvatarIcon from './icons/Avatar.svelte'
export let avatar: string | null | undefined = undefined
@@ -35,8 +50,7 @@
})
} else if (avatar) {
const avatarProviderId = getAvatarProviderId(avatar)
avatarProvider =
avatarProviderId && (await getClient().findOne(contact.class.AvatarProvider, { _id: avatarProviderId }))
avatarProvider = avatarProviderId && (await getProvider(getClient(), avatarProviderId))
if (!avatarProvider || avatarProvider.type === AvatarType.COLOR) {
url = undefined
+5 -4
View File
@@ -1,7 +1,7 @@
import client from '@hcengineering/client'
import contact from '@hcengineering/contact'
import core, { Client, setCurrentAccount, Version } from '@hcengineering/core'
import login from '@hcengineering/login'
import login, { loginId } from '@hcengineering/login'
import { getMetadata, getResource, setMetadata } from '@hcengineering/platform'
import { fetchMetadataLocalStorage, getCurrentLocation, navigate, setMetadataLocalStorage } from '@hcengineering/ui'
import presentation from './plugin'
@@ -11,6 +11,7 @@ export let versionError: string | undefined = ''
export async function connect (title: string): Promise<Client | undefined> {
const loc = getCurrentLocation()
const ws = loc.path[1]
if (ws === undefined) return
const tokens: Record<string, string> = fetchMetadataLocalStorage(login.metadata.LoginTokens) ?? {}
const token = tokens[ws]
setMetadata(login.metadata.LoginToken, token)
@@ -19,7 +20,7 @@ export async function connect (title: string): Promise<Client | undefined> {
if (token === undefined || endpoint === null || email === null) {
navigate({
path: [login.component.LoginApp],
path: [loginId],
query: { navigateUrl: encodeURIComponent(JSON.stringify(loc)) }
})
return
@@ -35,7 +36,7 @@ export async function connect (title: string): Promise<Client | undefined> {
() => {
clearMetadata(ws)
navigate({
path: [login.component.LoginApp],
path: [loginId],
query: {}
})
}
@@ -50,7 +51,7 @@ export async function connect (title: string): Promise<Client | undefined> {
console.error('WARNING: no employee account found.')
clearMetadata(ws)
navigate({
path: [login.component.LoginApp],
path: [loginId],
query: { navigateUrl: encodeURIComponent(JSON.stringify(getCurrentLocation())) }
})
return
+2 -1
View File
@@ -56,7 +56,8 @@ export default plugin(presentationId, {
Add: '' as IntlString,
Edit: '' as IntlString,
SelectAvatar: '' as IntlString,
GravatarsManaged: '' as IntlString
GravatarsManaged: '' as IntlString,
InltPropsValue: '' as IntlString
},
metadata: {
RequiredVersion: '' as Metadata<string>,
+2 -2
View File
@@ -200,7 +200,7 @@ export async function copyTextToClipboard (text: string): Promise<void> {
/**
* @public
*/
export type AttributeCategory = 'attribute' | 'inplace' | 'collection' | 'array'
export type AttributeCategory = 'object' | 'attribute' | 'inplace' | 'collection' | 'array'
/**
* @public
@@ -215,7 +215,7 @@ export function getAttributePresenterClass (
attribute: AnyAttribute
): { attrClass: Ref<Class<Doc>>, category: AttributeCategory } {
let attrClass = attribute.type._class
let category: AttributeCategory = 'attribute'
let category: AttributeCategory = 'object'
if (hierarchy.isDerived(attrClass, core.class.RefTo)) {
attrClass = (attribute.type as RefTo<Doc>).to
category = 'attribute'
@@ -322,7 +322,11 @@
class="icon-button"
class:disabled={a.disabled}
use:tooltip={{ label: a.label }}
on:click={(evt) => handleAction(a, evt)}
on:click={(evt) => {
if (!a.disabled) {
handleAction(a, evt)
}
}}
>
<Icon icon={a.icon} size={'medium'} fill={a.fill} />
</div>
+22 -5
View File
@@ -381,11 +381,6 @@ input.search {
}
/* Margins & Paddings */
.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; }
.ml-1-5 { margin-left: .375rem; }
@@ -476,6 +471,28 @@ input.search {
.p-6 { padding: 1.5rem; }
.p-10 { padding: 2.5rem; }
.p-text { padding: .125rem .25rem; }
.p-text-2 { padding: .25rem .5rem; }
.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; }
.step-tb-2-accent + .step-tb-2-accent {
position: relative;
margin-top: .5rem;
&::before {
content: '';
position: absolute;
top: -.25rem;
left: 0;
width: 100%;
height: 1px;
background-color: var(--popup-bg-hover);
}
}
/* --------- */
.no-word-wrap { word-wrap: none; }
.relative { position: relative; }
+3
View File
@@ -275,6 +275,9 @@
min-height: 1px;
height: 1px;
background-color: var(--divider-color);
&.dark { background-color: var(--body-accent); }
&.noMargin { margin: 0; }
}
.antiSection {
+2 -2
View File
@@ -173,10 +173,10 @@
.antiCard-pool {
flex-direction: row;
align-items: center;
margin: 0 .75rem .75rem;
margin: 0 .5rem .25rem;
font-size: .75rem;
& > *:not(:last-child) { margin-right: .375rem; }
& > * { margin: 0 .25rem .5rem; }
}
.antiCard-footer {
direction: ltr;
+85
View File
@@ -0,0 +1,85 @@
<!--
// 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 IconDownOutline from './icons/DownOutline.svelte'
export let size: 'small' | 'medium' = 'medium'
export let fill = 'var(--dark-color)'
export let expanded: boolean = false
export let direction: 'left' | 'right' = 'right'
export let outline: boolean = false
export let marginRight: string | undefined = undefined
export let marginLeft: string | undefined = undefined
export let margin: string | undefined = undefined
</script>
<div
class="chevron {size} {direction}"
class:outline
class:expanded
style:margin-right={marginRight}
style:margin-left={marginLeft}
style:margin
>
{#if outline}
<IconDownOutline {size} {fill} />
{:else}
<svg {fill} viewBox="0 0 6 6" xmlns="http://www.w3.org/2000/svg">
<path d="M0,0L6,3L0,6Z" />
</svg>
{/if}
</div>
<style lang="scss">
.chevron {
flex-shrink: 0;
transition: transform 0.15s var(--timing-main);
&.outline {
transform-origin: center;
&.right {
transform: rotate(-90deg);
}
&.left {
transform: rotate(90deg);
}
&.expanded {
transform: rotate(0deg);
}
}
&:not(.outline) {
transform-origin: 35% center;
transform: rotate(0deg);
&.small {
width: 0.325rem;
height: 0.325rem;
}
&.medium {
width: 0.375rem;
height: 0.375rem;
}
&.right {
transform: rotate(0deg);
}
&.left {
transform: rotate(180deg);
}
&.expanded {
transform: rotate(90deg);
}
}
}
</style>
@@ -18,7 +18,7 @@
import { tweened } from 'svelte/motion'
export let isExpanded = false
export let duration = 200
export let duration = 150
export let easing: (t: number) => number = quintOut
const dispatch = createEventDispatcher()
+42 -31
View File
@@ -17,51 +17,62 @@
import ExpandCollapse from './ExpandCollapse.svelte'
import Icon from './Icon.svelte'
import Label from './Label.svelte'
import Chevron from './Chevron.svelte'
export let icon: Asset | undefined = undefined
export let label: IntlString | undefined = undefined
export let expanded: boolean = false
export let bordered: boolean = false
export let expandable = true
export let contentColor = false
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="flex-grow flex" class:expanded class:expandable>
<div
class="fs-title flex-row-center mr-4"
on:click|stopPropagation={() => {
expanded = !expanded
}}
>
<div class="chevron" class:expanded></div>
<div class="an-element__icon">
<div class="flex-col">
<div class="expandable-header flex-between" class:expanded class:bordered>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="flex-row-center mr-4"
class:cursor-pointer={expandable}
on:click|stopPropagation={() => {
if (expandable) expanded = !expanded
}}
>
<Chevron {expanded} marginRight={'.5rem'} />
{#if icon}
<Icon {icon} size={'small'} />
<div class="min-w-4 mr-2">
<Icon {icon} size={'small'} />
</div>
{/if}
<span class="fs-title overflow-label" class:content-color={contentColor}>
{#if label}<Label {label} />{/if}<slot name="title" />
</span>
</div>
<span class="an-element__label title">
{#if label}<Label {label} />{/if}
<slot name="title" />
</span>
{#if $$slots.tools}
<div class="buttons-group small-gap">
<slot name="tools" />
</div>
{/if}
</div>
<slot name="tools" />
</div>
<ExpandCollapse isExpanded={expanded}>
<div class="antiComponent p-2">
<ExpandCollapse isExpanded={expanded}>
<slot />
</div>
</ExpandCollapse>
</ExpandCollapse>
</div>
<style lang="scss">
.expandable {
.chevron {
content: '▶';
margin-right: 0.5rem;
font-size: 0.75rem;
color: var(--dark-color);
&.expanded {
transform: rotateZ(90deg);
}
.expandable-header {
transition: margin-bottom 0.15s var(--timing-main);
&:not(.expanded) {
margin-bottom: 0;
}
&.expanded {
margin-bottom: 0.75rem;
}
&.bordered {
padding: 0.25rem 0.5rem;
background-color: var(--body-accent);
border: 1px solid var(--divider-color);
border-radius: 0.25rem;
}
}
</style>
+13 -5
View File
@@ -19,11 +19,19 @@
export let label: IntlString
export let params: Record<string, any> = {}
$: translation = translate(label, params)
let _value: string | undefined = undefined
$: if (label !== undefined) {
translate(label, params ?? {}).then((r) => {
_value = r
})
} else {
_value = label
}
</script>
{#await translation}
{#if _value}
{_value}
{:else}
{label}
{:then text}
{text}
{/await}
{/if}
+3 -1
View File
@@ -18,6 +18,7 @@
export let selection: number = 0
export let count: number
export let addClass: string | undefined = undefined
const refs: HTMLElement[] = []
@@ -66,8 +67,9 @@
>
{#each Array(count) as _, row}
<slot name="category" item={row} />
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="list-item"
class="list-item{addClass ? ` ${addClass}` : ''}"
class:selection={row === selection}
on:mouseover={() => onRow(row)}
on:focus={() => {}}
+2 -2
View File
@@ -32,7 +32,7 @@
let time: string = ''
async function formatTime (now: number) {
async function formatTime (now: number, value: number) {
let passed = now - value
if (passed < 0) passed = 0
if (passed < HOUR) {
@@ -48,7 +48,7 @@
}
}
$: formatTime($ticker)
$: formatTime($ticker, value)
$: tooltipValue = new Date(value).toLocaleString('default', {
minute: '2-digit',
@@ -1,6 +1,6 @@
<script lang="ts">
export let size: 'x-small' | 'small' | 'medium' | 'large' | 'full'
const fill: string = 'currentColor'
export let fill: string = 'currentColor'
</script>
<svg class="svg-{size}" {fill} viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+1
View File
@@ -99,6 +99,7 @@ export { default as ErrorPresenter } from './components/ErrorPresenter.svelte'
export { default as Scroller } from './components/Scroller.svelte'
export { default as ScrollerBar } from './components/ScrollerBar.svelte'
export { default as TabList } from './components/TabList.svelte'
export { default as Chevron } from './components/Chevron.svelte'
export { default as IconAdd } from './components/icons/Add.svelte'
export { default as IconBack } from './components/icons/Back.svelte'
@@ -32,7 +32,7 @@
TimeSince
} from '@hcengineering/ui'
import type { AttributeModel } from '@hcengineering/view'
import { getActions } from '@hcengineering/view-resources'
import { getActions, ObjectPresenter } from '@hcengineering/view-resources'
import { ActivityKey, DisplayTx } from '../activity'
import activity from '../plugin'
import TxViewTx from './TxViewTx.svelte'
@@ -215,7 +215,11 @@
<div class="strong">
<div class="flex flex-wrap gap-2" class:emphasized={value.added.length > 1}>
{#each value.added as cvalue}
<svelte:component this={m.presenter} value={cvalue} />
{#if value.isObjectAdded}
<ObjectPresenter value={cvalue} />
{:else}
<svelte:component this={m.presenter} value={cvalue} />
{/if}
{/each}
</div>
</div>
@@ -234,11 +238,15 @@
<div class="strong">
<div class="flex flex-wrap gap-2 flex-grow" class:emphasized={value.removed.length > 1}>
{#each value.removed as cvalue}
<svelte:component this={m.presenter} value={cvalue} />
{#if value.isObjectRemoved}
<ObjectPresenter value={cvalue} />
{:else}
<svelte:component this={m.presenter} value={cvalue} />
{/if}
{/each}
</div>
</div>
{:else if value.set === null || value.set === undefined}
{:else if value.set === null || value.set === undefined || value.set === ''}
<span class="lower"><Label label={activity.string.Unset} /> <Label label={m.label} /></span>
{:else}
<span class="lower" class:flex-grow={hasMessageType}>
@@ -251,11 +259,19 @@
{/if}
{#if isMessageType(m.attribute)}
<div class="strong message emphasized">
<svelte:component this={m.presenter} value={value.set} />
{#if value.isObjectSet}
<ObjectPresenter value={value.set} />
{:else}
<svelte:component this={m.presenter} value={value.set} />
{/if}
</div>
{:else}
<div class="strong">
<svelte:component this={m.presenter} value={value.set} />
{#if value.isObjectSet}
<ObjectPresenter value={value.set} />
{:else}
<svelte:component this={m.presenter} value={value.set} />
{/if}
</div>
{/if}
{/if}
@@ -264,7 +280,7 @@
{:else if viewlet === undefined && model.length > 0 && tx.mixinTx}
{#each model as m}
{#await getValue(client, m, tx) then value}
{#if value.set === null}
{#if value.set === null || value.set === ''}
<span>
<Label label={activity.string.Unset} /> <span class="lower"><Label label={m.label} /></span>
</span>
@@ -276,11 +292,19 @@
</span>
{#if isMessageType(m.attribute)}
<div class="strong message emphasized">
<svelte:component this={m.presenter} value={value.set} />
{#if value.isObjectSet}
<ObjectPresenter value={value.set} />
{:else}
<svelte:component this={m.presenter} value={value.set} />
{/if}
</div>
{:else}
<div class="strong">
<svelte:component this={m.presenter} value={value.set} />
{#if value.isObjectSet}
<ObjectPresenter value={value.set} />
{:else}
<svelte:component this={m.presenter} value={value.set} />
{/if}
</div>
{/if}
{/if}
+31 -10
View File
@@ -224,10 +224,14 @@ async function buildRemovedDoc (
return doc
}
async function getAllRealValues (client: TxOperations, values: any[], _class: Ref<Class<Doc>>): Promise<any[]> {
if (values.length === 0) return []
async function getAllRealValues (
client: TxOperations,
values: any[],
_class: Ref<Class<Doc>>
): Promise<[any[], boolean]> {
if (values.length === 0) return [[], false]
if (values.some((value) => typeof value !== 'string')) {
return values
return [values, false]
}
if (
_class === core.class.TypeString ||
@@ -235,12 +239,12 @@ async function getAllRealValues (client: TxOperations, values: any[], _class: Re
_class === core.class.TypeNumber ||
_class === core.class.TypeDate
) {
return values
return [values, false]
}
const realValues = await client.findAll(_class, { _id: { $in: values } })
const realValuesIds = realValues.map(({ _id }) => _id)
return [
const res = [
...realValues,
...(await Promise.all(
values
@@ -248,6 +252,7 @@ async function getAllRealValues (client: TxOperations, values: any[], _class: Re
.map(async (value) => await buildRemovedDoc(client, value, _class))
))
].filter((v) => v != null)
return [res, true]
}
function combineAttributes (attributes: any[], key: string, operator: string, arrayKey: string): any[] {
@@ -260,15 +265,31 @@ function combineAttributes (attributes: any[], key: string, operator: string, ar
).filter((v) => v != null)
}
export async function getValue (client: TxOperations, m: AttributeModel, tx: DisplayTx): Promise<any> {
interface TxAttributeValue {
set: any
isObjectSet: boolean
added: any[]
isObjectAdded: boolean
removed: any[]
isObjectRemoved: boolean
}
export async function getValue (client: TxOperations, m: AttributeModel, tx: DisplayTx): Promise<TxAttributeValue> {
const utxs = getModifiedAttributes(tx)
const value = {
const added = await getAllRealValues(client, combineAttributes(utxs, m.key, '$push', '$each'), m._class)
const removed = await getAllRealValues(client, combineAttributes(utxs, m.key, '$pull', '$in'), m._class)
const value: TxAttributeValue = {
set: utxs[0][m.key],
added: await getAllRealValues(client, combineAttributes(utxs, m.key, '$push', '$each'), m._class),
removed: await getAllRealValues(client, combineAttributes(utxs, m.key, '$pull', '$in'), m._class)
isObjectSet: false,
added: added[0],
isObjectAdded: added[1],
removed: removed[0],
isObjectRemoved: removed[1]
}
if (value.set !== undefined) {
;[value.set] = await getAllRealValues(client, [value.set], m._class)
const res = await getAllRealValues(client, [value.set], m._class)
value.set = res[0][0]
value.isObjectSet = res[1]
}
return value
}
@@ -23,7 +23,6 @@ export default mergeIds(attachmentId, attachment, {
string: {
NoAttachments: '' as IntlString,
UploadDropFilesHere: '' as IntlString,
Attachments: '' as IntlString,
Photos: '' as IntlString,
FileBrowser: '' as IntlString,
FileBrowserFileCounter: '' as IntlString,
+2 -1
View File
@@ -86,6 +86,7 @@ export default plugin(attachmentId, {
FileBrowserTypeFilterAudio: '' as IntlString,
FileBrowserTypeFilterVideos: '' as IntlString,
FileBrowserTypeFilterPDFs: '' as IntlString,
DeleteFile: '' as IntlString
DeleteFile: '' as IntlString,
Attachments: '' as IntlString
}
})
@@ -75,9 +75,7 @@
<div class="antiComponent max-w-240 flex-grow p-1">
<Expandable icon={ofClass?.icon} label={getEmbeddedLabel(typeTitle?.label ?? '')}>
<svelte:fragment slot="tools">
<div class="flex flex-reverse flex-grow">
<Button icon={IconDelete} on:click={() => client.remove(mapping)} size={'small'} />
</div>
<Button icon={IconDelete} on:click={() => client.remove(mapping)} size={'small'} />
</svelte:fragment>
<Expandable label={getEmbeddedLabel('Options')}>
<div class="flex-col">
@@ -323,44 +323,43 @@
<Expandable label={getEmbeddedLabel(mapping.type)}>
<svelte:fragment slot="tools">
<div class="flex-row-center">
<SpaceSelect
_class={core.class.Space}
label={core.string.Space}
bind:value={space}
on:change={(evt) => {
space = evt.detail
<SpaceSelect
_class={core.class.Space}
label={core.string.Space}
bind:value={space}
on:change={(evt) => {
space = evt.detail
}}
autoSelect
spaceQuery={{ _id: { $in: [contact.space.Contacts] } }}
/>
<DropdownLabels
label={getEmbeddedLabel('Direction')}
items={[
{ id: 'ASC', label: 'Ascending' },
{ id: 'DSC', label: 'Descending' }
]}
bind:selected={direction}
/>
<div class="fs-title">
<NumberEditor
kind={'button'}
value={limit}
focus={false}
placeholder={getEmbeddedLabel('Limit')}
onChange={(val) => {
if (val) {
limit = val
}
}}
autoSelect
spaceQuery={{ _id: { $in: [contact.space.Contacts] } }}
/>
<DropdownLabels
label={getEmbeddedLabel('Direction')}
items={[
{ id: 'ASC', label: 'Ascending' },
{ id: 'DSC', label: 'Descending' }
]}
bind:selected={direction}
/>
<div class="fs-title flex-row-center">
<NumberEditor
kind={'button'}
value={limit}
focus={false}
placeholder={getEmbeddedLabel('Limit')}
onChange={(val) => {
if (val) {
limit = val
}
}}
/>
</div>
<div class="ml-2 flex-row-center">
<div class="p-1">
{state}
</div>
<Button size={'large'} label={getEmbeddedLabel('Synchronize')} {loading} on:click={doSync} />
</div>
<div class="buttons-divider" />
<div class="flex-row-center">
<div class="p-1">
{state}
</div>
<Button size={'large'} label={getEmbeddedLabel('Synchronize')} {loading} on:click={doSync} />
</div>
</svelte:fragment>
<div class="flex-row flex-grow bottom-divider p-2">
@@ -15,7 +15,7 @@
-->
<script lang="ts">
import board, { Card } from '@hcengineering/board'
import { Class, Doc, FindOptions, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import { Class, Doc, DocumentQuery, FindOptions, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import { Kanban as KanbanUI } from '@hcengineering/kanban'
import { createQuery, getClient } from '@hcengineering/presentation'
import type { Kanban, SpaceWithStates, State } from '@hcengineering/task'
@@ -37,7 +37,7 @@
export let _class: Ref<Class<Card>>
export let space: Ref<SpaceWithStates>
export let search: string
export let query: DocumentQuery<Card>
export let options: FindOptions<Card> | undefined
let kanban: Kanban
@@ -95,6 +95,8 @@
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
}
$: resultQuery = { ...query, doneState: null, isArchived: { $nin: [true] }, space }
</script>
<ActionContext
@@ -105,12 +107,10 @@
<KanbanUI
bind:this={kanbanUI}
{_class}
{search}
{options}
query={{ doneState: null, isArchived: { $nin: [true] }, space }}
query={resultQuery}
{states}
fieldName={'state'}
rankFieldName={'rank'}
on:content={(evt) => {
listProvider.update(evt.detail)
}}
@@ -23,7 +23,7 @@
{_class}
config={[
'title',
'$lookup.state',
'state',
{
key: '',
presenter: tags.component.TagsPresenter,
@@ -40,7 +40,6 @@
export let options: FindOptions<Event> | undefined = undefined
export let baseMenuClass: Ref<Class<Event>> | undefined = undefined
export let config: (string | BuildModelKey)[]
export let search: string = ''
export let createComponent: AnyComponent | undefined = undefined
const mondayStart = true
@@ -49,10 +48,6 @@
let currentDate: Date = new Date()
let selectedDate: Date = new Date()
let resultQuery: DocumentQuery<Event>
$: spaceOpt = space ? { space } : {}
$: resultQuery = search === '' ? { ...query, ...spaceOpt } : { ...query, $search: search, ...spaceOpt }
let objects: Event[] = []
const q = createQuery()
@@ -67,7 +62,7 @@
{ sort: { date: SortingOrder.Ascending }, ...options }
)
}
$: update(_class, resultQuery, options)
$: update(_class, query, options)
function areDatesLess (firstDate: Date, secondDate: Date): boolean {
return (
@@ -256,7 +251,7 @@
{today}
{selected}
{wrongMonth}
query={resultQuery}
{query}
/>
</svelte:fragment>
</YearCalendar>
@@ -276,7 +271,7 @@
{today}
{selected}
{wrongMonth}
query={resultQuery}
{query}
on:select={(e) => {
currentDate = e.detail
if (areDatesEqual(selectedDate, currentDate)) {
@@ -30,7 +30,7 @@
TabList
} from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { FilterButton, ViewletSettingButton } from '@hcengineering/view-resources'
import { FilterButton, getViewOptions, ViewletSettingButton } from '@hcengineering/view-resources'
import calendar from '../plugin'
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
@@ -101,6 +101,8 @@
})
$: twoRows = $deviceInfo.twoRows
$: viewOptions = getViewOptions(selectedViewlet)
</script>
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
@@ -133,7 +135,7 @@
}}
/>
{/if}
<ViewletSettingButton viewlet={selectedViewlet} />
<ViewletSettingButton bind:viewOptions viewlet={selectedViewlet} />
</div>
</div>
@@ -148,6 +150,7 @@
space,
options: selectedViewlet.options,
config: preference?.config ?? selectedViewlet.config,
viewOptions,
viewlet: selectedViewlet,
query: resultQuery,
search,
+1 -1
View File
@@ -41,9 +41,9 @@ export default async () => {
): Promise<Client> => {
if (client instanceof Promise) {
client = await client
await client.close()
}
if (token !== _token && client !== undefined) {
await client.close()
client = undefined
}
if (client === undefined) {
+2 -1
View File
@@ -72,6 +72,7 @@
"Birthday": "Birthday",
"UseImage": "Upload an image",
"UseGravatar": "Use Gravatar",
"UseColor": "Use color"
"UseColor": "Use color",
"NotSpecified": "Not specified"
}
}
+2 -1
View File
@@ -72,6 +72,7 @@
"Birthday": "День рождения",
"UseImage": "Загрузить фото",
"UseGravatar": "Использовать Gravatar",
"UseColor": "Использовать цвет"
"UseColor": "Использовать цвет",
"NotSpecified": "Не указан"
}
}
@@ -0,0 +1,33 @@
<!--
// 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 { Contact } from '@hcengineering/contact'
import { Ref } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import contact from '../plugin'
import ContactPresenter from './ContactPresenter.svelte'
export let value: Ref<Contact>
export let isInteractive = true
let doc: Contact | undefined
const query = createQuery()
$: value && query.query(contact.class.Contact, { _id: value }, (res) => ([doc] = res), { limit: 1 })
</script>
{#if doc}
<ContactPresenter value={doc} {isInteractive} />
{/if}
@@ -18,7 +18,13 @@
import { createQuery, getClient } from '@hcengineering/presentation'
import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { ActionContext, FilterButton, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
import {
ActionContext,
FilterButton,
getViewOptions,
TableBrowser,
ViewletSettingButton
} from '@hcengineering/view-resources'
import contact from '../plugin'
import CreateContact from './CreateContact.svelte'
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
@@ -64,6 +70,8 @@
}
$: twoRows = $deviceInfo.twoRows
$: viewOptions = getViewOptions(viewlet)
</script>
<ActionContext
@@ -95,7 +103,7 @@
size={'small'}
on:click={(ev) => showCreateDialog(ev)}
/>
<ViewletSettingButton {viewlet} />
<ViewletSettingButton bind:viewOptions {viewlet} />
</div>
</div>
@@ -2,21 +2,14 @@
import { Employee } from '@hcengineering/contact'
import { WithLookup } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import type { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
import { showPopup } from '@hcengineering/ui'
import { PersonLabelTooltip } from '..'
import PersonPresenter from '../components/PersonPresenter.svelte'
import EmployeePreviewPopup from './EmployeePreviewPopup.svelte'
import EmployeeStatusPresenter from './EmployeeStatusPresenter.svelte'
export let value: WithLookup<Employee> | null | undefined
export let tooltipLabels:
| {
personLabel?: IntlString
placeholderLabel?: IntlString
component?: AnySvelteComponent | AnyComponent
props?: any
}
| undefined = undefined
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
export let shouldShowAvatar: boolean = true
export let shouldShowName: boolean = true
export let shouldShowPlaceholder = false
@@ -25,6 +18,7 @@
export let isInteractive = true
export let inline = false
export let disableClick = false
export let defaultName: IntlString | undefined = undefined
let container: HTMLElement
@@ -48,7 +42,7 @@
$: handlePersonEdit = onEmployeeEdit ?? onEdit
</script>
<div bind:this={container} class:over-underline={!inline}>
<div bind:this={container}>
<PersonPresenter
{value}
{tooltipLabels}
@@ -59,6 +53,7 @@
{shouldShowPlaceholder}
{isInteractive}
{inline}
{defaultName}
/>
</div>
{#if value?.$lookup?.statuses?.length}
@@ -0,0 +1,39 @@
<script lang="ts">
import { Employee } from '@hcengineering/contact'
import { Ref } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { ButtonKind } from '@hcengineering/ui'
import { PersonLabelTooltip } from '..'
import contact from '../plugin'
import EmployeePresenter from './EmployeePresenter.svelte'
export let value: Ref<Employee> | null | undefined
export let kind: ButtonKind = 'link'
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
let employee: Employee | undefined
const query = createQuery()
$: value && query.query(contact.class.Employee, { _id: value }, (res) => ([employee] = res), { limit: 1 })
function getValue (
employee: Employee | undefined,
value: Ref<Employee> | null | undefined
): Employee | null | undefined {
if (value === undefined || value === null) {
return value
}
return employee
}
</script>
<EmployeePresenter
value={getValue(employee, value)}
{tooltipLabels}
isInteractive={false}
shouldShowAvatar
shouldShowPlaceholder
defaultName={contact.string.NotSpecified}
shouldShowName={kind !== 'list'}
avatarSize={kind === 'list-header' ? 'small' : 'x-small'}
disableClick
/>
@@ -18,7 +18,7 @@
import { createQuery, getClient, UsersPopup, IconMembersOutline } from '@hcengineering/presentation'
import { Button, IconAdd, Label, showPopup, Icon } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { Table, ViewletSettingButton } from '@hcengineering/view-resources'
import { getViewOptions, Table, ViewletSettingButton } from '@hcengineering/view-resources'
import contact from '../plugin'
export let objectId: Ref<Doc>
@@ -90,6 +90,7 @@
}
})
}
$: viewOptions = getViewOptions(descr)
</script>
<div class="antiSection">
@@ -101,7 +102,7 @@
<Label label={contact.string.Members} />
</span>
<div class="buttons-group xsmall-gap">
<ViewletSettingButton viewlet={descr} />
<ViewletSettingButton bind:viewOptions viewlet={descr} />
<Button id={contact.string.AddMember} icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={createApp} />
</div>
</div>
@@ -118,6 +119,7 @@
<span class="dark-color">
<Label label={contact.string.NoMembers} />
</span>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span class="over-underline content-accent-color" on:click={createApp}>
<Label label={contact.string.AddMember} />
</span>
@@ -15,8 +15,10 @@
<script lang="ts">
import { formatName, Person } from '@hcengineering/contact'
import { IntlString } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import { LabelAndProps } from '@hcengineering/ui'
import { PersonLabelTooltip } from '..'
import PersonContent from './PersonContent.svelte'
import type { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
export let value: Person | null | undefined
export let inline = false
@@ -26,27 +28,40 @@
export let shouldShowName = true
export let shouldShowPlaceholder = false
export let defaultName: IntlString | undefined = undefined
export let tooltipLabels:
| {
personLabel?: IntlString
placeholderLabel?: IntlString
component?: AnySvelteComponent | AnyComponent
props?: any
}
| undefined = undefined
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
export let avatarSize: 'inline' | 'tiny' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large' = 'x-small'
export let onEdit: ((event: MouseEvent) => void) | undefined = undefined
function getTooltip (
tooltipLabels: PersonLabelTooltip | undefined,
value: Person | null | undefined
): LabelAndProps | undefined {
if (!tooltipLabels) {
return !value
? undefined
: {
label: presentation.string.InltPropsValue,
props: { value: formatName(value.name) }
}
}
const component = value ? tooltipLabels.component : undefined
const label = value
? tooltipLabels.personLabel
? tooltipLabels.personLabel
: presentation.string.InltPropsValue
: undefined
const props = tooltipLabels.props ? tooltipLabels.props : value ? { value: formatName(value.name) } : undefined
return {
component,
label,
props
}
}
</script>
{#if value || shouldShowPlaceholder}
<PersonContent
showTooltip={tooltipLabels
? {
label: value ? tooltipLabels.personLabel : undefined,
component: value ? tooltipLabels.component : undefined,
props: value && tooltipLabels.personLabel ? { value: formatName(value.name) } : tooltipLabels.props
}
: undefined}
showTooltip={getTooltip(tooltipLabels, value)}
{value}
{inline}
{onEdit}
@@ -0,0 +1,59 @@
<!--
// 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 contact, { Person } from '@hcengineering/contact'
import { Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { PersonLabelTooltip } from '..'
import PersonPresenter from './PersonPresenter.svelte'
export let value: Ref<Person> | null | undefined
export let inline = false
export let enlargedText = false
export let isInteractive = true
export let shouldShowAvatar = true
export let shouldShowName = true
export let shouldShowPlaceholder = false
export let defaultName: IntlString | undefined = undefined
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
export let avatarSize: 'inline' | 'tiny' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large' = 'x-small'
export let onEdit: ((event: MouseEvent) => void) | undefined = undefined
let person: Person | undefined
const query = createQuery()
$: value && query.query(contact.class.Person, { _id: value }, (res) => ([person] = res), { limit: 1 })
function getValue (person: Person | undefined, value: Ref<Person> | null | undefined): Person | null | undefined {
if (value === undefined || value === null) {
return value
}
return person
}
</script>
<PersonPresenter
value={getValue(person, value)}
{onEdit}
{avatarSize}
{tooltipLabels}
{defaultName}
{shouldShowAvatar}
{shouldShowName}
{shouldShowPlaceholder}
{enlargedText}
{isInteractive}
{inline}
/>
+18 -3
View File
@@ -17,9 +17,9 @@
import { Channel, Contact, Employee, formatName, getGravatarUrl } from '@hcengineering/contact'
import { Class, Client, DocumentQuery, Ref, RelatedDocument, WithLookup } from '@hcengineering/core'
import { leaveWorkspace } from '@hcengineering/login-resources'
import { Resources } from '@hcengineering/platform'
import { IntlString, Resources } from '@hcengineering/platform'
import { Avatar, getClient, MessageBox, ObjectSearchResult, UserInfo, getFileUrl } from '@hcengineering/presentation'
import { showPopup } from '@hcengineering/ui'
import { AnyComponent, AnySvelteComponent, showPopup } from '@hcengineering/ui'
import Channels from './components/Channels.svelte'
import ChannelsDropdown from './components/ChannelsDropdown.svelte'
import ChannelsEditor from './components/ChannelsEditor.svelte'
@@ -48,7 +48,11 @@ import OrganizationPresenter from './components/OrganizationPresenter.svelte'
import PersonEditor from './components/PersonEditor.svelte'
import PersonPresenter from './components/PersonPresenter.svelte'
import SocialEditor from './components/SocialEditor.svelte'
import ContactRefPresenter from './components/ContactRefPresenter.svelte'
import PersonRefPresenter from './components/PersonRefPresenter.svelte'
import EmployeeRefPresenter from './components/EmployeeRefPresenter.svelte'
import contact from './plugin'
import { employeeSort } from './utils'
export {
Channels,
@@ -117,6 +121,13 @@ async function openChannelURL (doc: Channel): Promise<void> {
}
}
export interface PersonLabelTooltip {
personLabel?: IntlString
placeholderLabel?: IntlString
component?: AnySvelteComponent | AnyComponent
props?: any
}
export default async (): Promise<Resources> => ({
actionImpl: {
KickEmployee: kickEmployee,
@@ -126,6 +137,8 @@ export default async (): Promise<Resources> => ({
PersonEditor,
OrganizationEditor,
ContactPresenter,
ContactRefPresenter,
PersonRefPresenter,
PersonPresenter,
OrganizationPresenter,
ChannelsPresenter,
@@ -139,6 +152,7 @@ export default async (): Promise<Resources> => ({
Contacts,
EmployeeAccountPresenter,
EmployeePresenter,
EmployeeRefPresenter,
Members,
MemberPresenter,
EditMember,
@@ -164,6 +178,7 @@ export default async (): Promise<Resources> => ({
function: {
GetFileUrl: getFileUrl,
GetGravatarUrl: getGravatarUrl,
GetColorUrl: (uri: string) => uri
GetColorUrl: (uri: string) => uri,
EmployeeSort: employeeSort
}
})
+6 -1
View File
@@ -16,6 +16,7 @@
import contact, { contactId } from '@hcengineering/contact'
import { IntlString, mergeIds } from '@hcengineering/platform'
import { SortFunc } from '@hcengineering/view'
export default mergeIds(contactId, contact, {
string: {
@@ -61,6 +62,10 @@ export default mergeIds(contactId, contact, {
KickEmployeeDescr: '' as IntlString,
Email: '' as IntlString,
CreateEmployee: '' as IntlString,
Inactive: '' as IntlString
Inactive: '' as IntlString,
NotSpecified: '' as IntlString
},
function: {
EmployeeSort: '' as SortFunc
}
})
+34 -2
View File
@@ -14,9 +14,9 @@
// limitations under the License.
//
import contact, { ChannelProvider } from '@hcengineering/contact'
import contact, { ChannelProvider, Employee, formatName } from '@hcengineering/contact'
import { Ref, Timestamp } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { createQuery, getClient } from '@hcengineering/presentation'
const client = getClient()
const channelProviders = client.findAll(contact.class.ChannelProvider, {})
@@ -38,3 +38,35 @@ export function formatDate (dueDateMs: Timestamp): string {
minute: '2-digit'
})
}
export async function employeeSort (value: Array<Ref<Employee>>): Promise<Array<Ref<Employee>>> {
return await new Promise((resolve) => {
const query = createQuery(true)
query.query(contact.class.Employee, { _id: { $in: value } }, (res) => {
const employees = new Map(res.map((x) => [x._id, x]))
value.sort((a, b) => {
const employeeId1 = a as Ref<Employee> | null | undefined
const employeeId2 = b as Ref<Employee> | null | undefined
if (employeeId1 == null && employeeId2 != null) {
return 1
}
if (employeeId1 != null && employeeId2 == null) {
return -1
}
if (employeeId1 != null && employeeId2 != null) {
const name1 = formatName(employees.get(employeeId1)?.name ?? '')
const name2 = formatName(employees.get(employeeId2)?.name ?? '')
return name1.localeCompare(name2)
}
return 0
})
resolve(value)
query.unsubscribe()
})
})
}
@@ -20,7 +20,13 @@
import { createQuery, getClient } from '@hcengineering/presentation'
import { deviceOptionsStore as deviceInfo, Icon, Label, Loading, SearchEdit } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { ActionContext, FilterButton, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
import {
ActionContext,
FilterButton,
getViewOptions,
TableBrowser,
ViewletSettingButton
} from '@hcengineering/view-resources'
import document from '../plugin'
export let query: DocumentQuery<Document> = {}
@@ -63,6 +69,8 @@
let twoRows: boolean
$: twoRows = $deviceInfo.docWidth <= 680
$: viewOptions = getViewOptions(viewlet)
</script>
<ActionContext
@@ -87,7 +95,7 @@
/>
</div>
<div class="ac-header-full" class:secondRow={twoRows}>
<ViewletSettingButton {viewlet} />
<ViewletSettingButton bind:viewOptions {viewlet} />
</div>
</div>
@@ -19,7 +19,7 @@
import { createQuery, getClient, UsersPopup } from '@hcengineering/presentation'
import { Button, eventToHTMLElement, IconAdd, Label, Scroller, showPopup } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import { Table, ViewletSettingButton } from '@hcengineering/view-resources'
import { getViewOptions, Table, ViewletSettingButton } from '@hcengineering/view-resources'
import hr from '../plugin'
import { addMember } from '../utils'
@@ -91,6 +91,8 @@
}
})
}
$: viewOptions = getViewOptions(descr)
</script>
<div class="antiSection">
@@ -99,7 +101,7 @@
<Label label={hr.string.Members} />
</span>
<div class="buttons-group xsmall-gap">
<ViewletSettingButton viewlet={descr} />
<ViewletSettingButton bind:viewOptions viewlet={descr} />
<Button id={hr.string.AddEmployee} icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={add} />
</div>
</div>
@@ -20,7 +20,7 @@
import { createQuery, getClient } from '@hcengineering/presentation'
import { Button, Label, Loading, Scroller, tableSP } from '@hcengineering/ui'
import view, { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
import { Table, ViewletSettingButton } from '@hcengineering/view-resources'
import { getViewOptions, Table, ViewletSettingButton } from '@hcengineering/view-resources'
import hr from '../../plugin'
import {
EmployeeReports,
@@ -215,6 +215,8 @@
}
return result
}
$: viewOptions = getViewOptions(descr)
</script>
{#if departmentStaff.length}
@@ -226,7 +228,7 @@
{:else}
<div class="flex-row-center flex-reverse">
<div class="ml-1">
<ViewletSettingButton viewlet={descr} />
<ViewletSettingButton bind:viewOptions viewlet={descr} />
</div>
<Button
label={getEmbeddedLabel('Export')}
@@ -0,0 +1,33 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Ref } from '@hcengineering/core'
import inventory, { Category } from '@hcengineering/inventory'
import { createQuery } from '@hcengineering/presentation'
import CategoryPresenter from './CategoryPresenter.svelte'
export let value: Ref<Category>
export let inline: boolean = false
let category: Category | undefined
const query = createQuery()
$: query.query(inventory.class.Category, { _id: value }, (res) => ([category] = res), { limit: 1 })
</script>
{#if category}
<CategoryPresenter {inline} value={category} />
{/if}
+2
View File
@@ -19,6 +19,7 @@ import { Resources } from '@hcengineering/platform'
import { showPopup } from '@hcengineering/ui'
import Categories from './components/Categories.svelte'
import CategoryPresenter from './components/CategoryPresenter.svelte'
import CategoryRefPresenter from './components/CategoryRefPresenter.svelte'
import CreateCategory from './components/CreateCategory.svelte'
import EditProduct from './components/EditProduct.svelte'
import ProductPresenter from './components/ProductPresenter.svelte'
@@ -37,6 +38,7 @@ export default async (): Promise<Resources> => ({
component: {
Categories,
CategoryPresenter,
CategoryRefPresenter,
ProductPresenter,
EditProduct,
Variants,
+2 -1
View File
@@ -30,6 +30,7 @@
"Description": "Description",
"FullDescription": "Full description",
"FunnelPlaceholder": "The simple funnel",
"Members": "Members"
"Members": "Members",
"UnAssign": "Unassign"
}
}
+3 -2
View File
@@ -17,7 +17,7 @@
"Leads": "Сделки",
"SelectCustomer": "Выбрать клиента",
"Lead": "Сделка",
"Assignee": "Назначена",
"Assignee": "Исполнитель",
"Title": "Загаловок",
"LeadPlaceholder": "Простая сделка",
"ManageFunnelStatuses": "Управление статусами воронки",
@@ -30,6 +30,7 @@
"Description": "Описание",
"FullDescription": "Детальное описание",
"FunnelPlaceholder": "Простая воронка",
"Members": "Пользователи"
"Members": "Пользователи",
"UnAssign": "Отменить назначение"
}
}
@@ -14,11 +14,11 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Contact } from '@hcengineering/contact'
import contact, { Contact, Employee } from '@hcengineering/contact'
import { AttachedData, generateId, Ref, SortingOrder, Space } from '@hcengineering/core'
import type { Customer, Lead } from '@hcengineering/lead'
import type { Customer, Funnel, Lead } from '@hcengineering/lead'
import { OK, Status } from '@hcengineering/platform'
import { Card, getClient, SpaceSelector, UserBox } from '@hcengineering/presentation'
import { Card, createQuery, EmployeeBox, getClient, SpaceSelector, UserBox } from '@hcengineering/presentation'
import task, { calcRank } from '@hcengineering/task'
import { createFocusManager, EditBox, FocusHandler, Label, Status as StatusControl, Button } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
@@ -41,14 +41,20 @@
return (preserveCustomer || customer === undefined) && title === ''
}
$: client.findAll(lead.class.Funnel, {}).then((r) => {
if (r.find((it) => it._id === _space) === undefined) {
_space = r.shift()?._id as Ref<Space>
let funnels: Funnel[] = []
const funnelQuery = createQuery()
funnelQuery.query(lead.class.Funnel, {}, (res) => (funnels = res))
$: {
if (funnels.find((it) => it._id === _space) === undefined) {
_space = funnels[0]?._id
}
})
}
let assignee: Ref<Employee> | undefined = undefined
async function createLead () {
const state = await client.findOne(task.class.State, { space: _space })
const state = await client.findOne(task.class.State, { space: _space }, { sort: { rank: SortingOrder.Ascending } })
if (state === undefined) {
throw new Error('create application: state not found')
}
@@ -145,6 +151,13 @@
</div>
<svelte:fragment slot="pool">
<EmployeeBox
focusIndex={2}
label={lead.string.Assignee}
bind:value={assignee}
allowDeselect
titleDeselect={lead.string.UnAssign}
/>
{#if !preserveCustomer}
<UserBox
focusIndex={2}
@@ -42,7 +42,7 @@
<Scroller horizontal>
<Table
_class={lead.class.Lead}
config={['', '$lookup.state', '$lookup.doneState']}
config={['', 'state', 'doneState']}
query={{ attachedTo: objectId }}
{loadingProps}
/>
@@ -50,7 +50,7 @@
{:else}
<Table
_class={lead.class.Lead}
config={['', '$lookup.state', '$lookup.doneState']}
config={['', 'state', 'doneState']}
query={{ attachedTo: objectId }}
{loadingProps}
/>
@@ -21,8 +21,4 @@
export let value: Customer
</script>
<Table
_class={leads.class.Lead}
config={['', '$lookup.state', '$lookup.doneState']}
query={{ attachedTo: value._id }}
/>
<Table _class={leads.class.Lead} config={['', 'state', 'doneState']} query={{ attachedTo: value._id }} />
+3 -1
View File
@@ -41,7 +41,9 @@ export default mergeIds(leadId, lead, {
Description: '' as IntlString,
FullDescription: '' as IntlString,
FunnelPlaceholder: '' as IntlString,
Members: '' as IntlString
Members: '' as IntlString,
Assignee: '' as IntlString,
UnAssign: '' as IntlString
},
component: {
CreateCustomer: '' as AnyComponent,
@@ -20,13 +20,14 @@
import { createEventDispatcher } from 'svelte'
import login from '../plugin'
import InviteWorkspace from './icons/InviteWorkspace.svelte'
import { loginId } from '@hcengineering/login'
const dispatch = createEventDispatcher()
async function getLink (): Promise<string> {
const inviteId = await getInviteLink()
const loc = getCurrentLocation()
loc.path[0] = login.component.LoginApp
loc.path[0] = loginId
loc.path[1] = 'join'
loc.path.length = 2
loc.query = {
@@ -35,8 +35,8 @@
'',
'$lookup.space.name',
'$lookup.space.$lookup.company',
'$lookup.state',
'$lookup.doneState'
'state',
'doneState'
]
let wSection: number
</script>
@@ -23,7 +23,7 @@
<Table
_class={recruit.class.Applicant}
config={['', '$lookup.space.name', '$lookup.state', '$lookup.doneState']}
config={['', '$lookup.space.name', 'state', 'doneState']}
query={{ attachedTo: value._id }}
loadingProps={{ length: value.applications ?? 0 }}
/>
@@ -22,7 +22,7 @@
import { Vacancy } from '@hcengineering/recruit'
import { FullDescriptionBox } from '@hcengineering/text-editor'
import tracker from '@hcengineering/tracker'
import { Button, Component, EditBox, Grid, Icon, IconAdd, IconMoreH, Label, showPopup } from '@hcengineering/ui'
import { Button, Component, EditBox, Grid, IconMoreH, showPopup } from '@hcengineering/ui'
import { ClassAttributeBar, ContextMenu } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import recruit from '../plugin'
@@ -131,33 +131,7 @@
/>
<!-- <MembersBox label={recruit.string.Members} space={object} /> -->
<div class="antiSection">
<div class="antiSection-header">
<div class="antiSection-header__icon">
<Icon icon={tracker.icon.Issue} size={'small'} />
</div>
<span class="antiSection-header__title">
<Label label={recruit.string.RelatedIssues} />
</span>
<div class="buttons-group small-gap">
<Button
id="add-sub-issue"
width="min-content"
icon={IconAdd}
label={undefined}
labelParams={{ subIssues: 0 }}
kind={'transparent'}
size={'small'}
on:click={() => {
showPopup(tracker.component.CreateIssue, { relatedTo: object, space: object.space }, 'top')
}}
/>
</div>
</div>
<div class="flex-row">
<Component is={tracker.component.RelatedIssues} props={{ object }} />
</div>
</div></Grid
>
<Component is={tracker.component.RelatedIssuesSection} props={{ object, label: recruit.string.RelatedIssues }} />
</Grid>
</Panel>
{/if}
@@ -18,7 +18,7 @@
import { Vacancy } from '@hcengineering/recruit'
import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@hcengineering/ui'
import view, { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
import { FilterButton, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
import { FilterButton, getViewOptions, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
import recruit from '../plugin'
import CreateVacancy from './CreateVacancy.svelte'
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
@@ -132,6 +132,8 @@
}
$: twoRows = $deviceInfo.twoRows
$: viewOptions = getViewOptions(descr)
</script>
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
@@ -156,7 +158,7 @@
kind={'primary'}
on:click={showCreateDialog}
/>
<ViewletSettingButton viewlet={descr} />
<ViewletSettingButton bind:viewOptions viewlet={descr} />
</div>
</div>
@@ -35,7 +35,7 @@
<div class="popup-table">
<Table
_class={recruit.class.Applicant}
config={['', '$lookup.attachedTo', '$lookup.state', '$lookup.doneState', 'modifiedOn']}
config={['', 'attachedTo', 'state', 'doneState', 'modifiedOn']}
{options}
query={{ ...(resultQuery ?? {}), space: value }}
loadingProps={{ length: 0 }}
@@ -27,7 +27,7 @@
} from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import CategoryElement from './CategoryElement.svelte'
import login from '@hcengineering/login'
import login, { loginId } from '@hcengineering/login'
import { AccountRole, getCurrentAccount } from '@hcengineering/core'
import { EmployeeAccount } from '@hcengineering/contact'
import { setMetadata } from '@hcengineering/platform'
@@ -76,10 +76,10 @@
setMetadata(login.metadata.LoginToken, null)
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
setMetadataLocalStorage(login.metadata.LoginEmail, null)
navigate({ path: [login.component.LoginApp] })
navigate({ path: [loginId] })
}
function selectWorkspace (): void {
navigate({ path: [login.component.LoginApp, 'selectWorkspace'] })
navigate({ path: [loginId, 'selectWorkspace'] })
}
function inviteWorkspace (): void {
showPopup(login.component.InviteLink, {})
@@ -88,16 +88,7 @@
<TableBrowser
{_class}
config={[
'',
'$lookup.attachedTo',
'$lookup.assignee',
'$lookup.state',
'$lookup.doneState',
'attachments',
'comments',
'modifiedOn'
]}
config={['', 'attachedTo', 'assignee', 'state', 'doneState', 'attachments', 'comments', 'modifiedOn']}
query={resultQuery}
showNotification
/>
@@ -18,11 +18,11 @@
import type { DoneState, SpaceWithStates, State, Task } from '@hcengineering/task'
import task from '@hcengineering/task'
import { BarDashboard, DashboardItem } from '@hcengineering/ui'
import { FilterBar } from '@hcengineering/view-resources'
import CreateFilter from './CreateFilter.svelte'
export let _class: Ref<Class<Task>>
export let space: Ref<SpaceWithStates>
export let query: DocumentQuery<Task>
const client = getClient()
const hieararchy = client.getHierarchy()
@@ -85,15 +85,12 @@
const docQuery = createQuery()
$: query = modified
$: resultQuery = modified
? {
space,
_id: { $in: ids }
_id: { $in: ids },
...query
}
: { space }
let resultQuery = {
space
}
: query
function updateDocs (_class: Ref<Class<Task>>, states: State[], query: DocumentQuery<Task>): void {
if (states.length === 0) {
@@ -146,7 +143,6 @@
</script>
<CreateFilter bind:value={modified} />
<FilterBar {_class} {query} on:change={(e) => (resultQuery = e.detail)} />
<div class="ml-10 mt-4">
<BarDashboard {items} />
@@ -15,7 +15,7 @@
-->
<script lang="ts">
import { Class, DocumentQuery, FindOptions, Ref, SortingOrder } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { createQuery, getClient } from '@hcengineering/presentation'
import { DoneState, SpaceWithStates, State, Task } from '@hcengineering/task'
import { TabList } from '@hcengineering/ui'
import type { TabItem } from '@hcengineering/ui'
@@ -27,26 +27,26 @@
export let _class: Ref<Class<Task>>
export let space: Ref<SpaceWithStates>
export let query: DocumentQuery<Task>
export let options: FindOptions<Task> | undefined
export let config: string[]
export let search: string
let doneStatusesView: boolean = false
let state: Ref<State> | undefined = undefined
const selectedDoneStates: Set<Ref<DoneState>> = new Set<Ref<DoneState>>()
$: resConfig = updateConfig(config)
let query = {}
let doneStates: DoneState[] = []
let itemsDS: TabItem[] = []
let selectedDS: string[] = []
let withoutDone: boolean = false
let resultQuery: DocumentQuery<Task>
function updateConfig (config: string[]): string[] {
if (state !== undefined) {
return config.filter((p) => p !== '$lookup.state')
return config.filter((p) => p !== 'state')
}
if (selectedDoneStates.size === 1) {
return config.filter((p) => p !== '$lookup.doneState')
return config.filter((p) => p !== 'doneState')
}
return config
}
@@ -77,12 +77,11 @@
}
)
async function updateQuery (search: string, selectedDoneStates: Set<Ref<DoneState>>): Promise<void> {
const client = getClient()
async function updateQuery (query: DocumentQuery<Task>, selectedDoneStates: Set<Ref<DoneState>>): Promise<void> {
resConfig = updateConfig(config)
const result = {} as DocumentQuery<Task>
if (search !== '') {
result.$search = search
}
const result = client.getHierarchy().clone(query)
result.space = space
if (state) {
result.state = state
@@ -94,7 +93,7 @@
} else if (withoutDone) {
result.doneState = null
}
query = result
resultQuery = result
}
function doneStateClick (id: Ref<DoneState>): void {
@@ -108,17 +107,17 @@
selectedDS = ['NoDoneState']
withoutDone = true
}
updateQuery(search, selectedDoneStates)
updateQuery(query, selectedDoneStates)
}
function noDoneClick (): void {
withoutDone = true
selectedDS = ['NoDoneState']
selectedDoneStates.clear()
updateQuery(search, selectedDoneStates)
updateQuery(query, selectedDoneStates)
}
$: updateQuery(search, selectedDoneStates)
$: updateQuery(query, selectedDoneStates)
const handleSelect = (result: any) => {
if (result.type === 'select') {
const res = result.detail
@@ -127,12 +126,12 @@
state = undefined
withoutDone = false
selectedDoneStates.clear()
updateQuery(search, selectedDoneStates)
updateQuery(query, selectedDoneStates)
} else if (res.id === 'DoneStates') {
doneStatusesView = true
state = undefined
selectedDoneStates.clear()
updateQuery(search, selectedDoneStates)
updateQuery(query, selectedDoneStates)
}
}
}
@@ -158,11 +157,11 @@
{#if doneStatusesView}
<TabList items={itemsDS} bind:selected={selectedDS} multiselect on:select={handleDoneSelect} size={'small'} />
{:else}
<StatesBar bind:state {space} gap={'none'} on:change={() => updateQuery(search, selectedDoneStates)} />
<StatesBar bind:state {space} gap={'none'} on:change={() => updateQuery(query, selectedDoneStates)} />
{/if}
</div>
<div class="statustableview-container">
<TableBrowser {_class} bind:query config={resConfig} {options} showNotification />
<TableBrowser {_class} bind:query={resultQuery} config={resConfig} {options} showNotification />
</div>
<style lang="scss">
@@ -14,7 +14,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, FindOptions, Ref, SortingOrder } from '@hcengineering/core'
import { Class, Doc, DocumentQuery, FindOptions, Ref, SortingOrder } from '@hcengineering/core'
import { Kanban as KanbanUI } from '@hcengineering/kanban'
import { getResource } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
@@ -23,7 +23,6 @@
import { getEventPositionElement, showPopup } from '@hcengineering/ui'
import {
ActionContext,
FilterBar,
focusStore,
ListSelectionProvider,
SelectDirection,
@@ -35,8 +34,7 @@
export let _class: Ref<Class<Task>>
export let space: Ref<SpaceWithStates>
// export let open: AnyComponent
export let search: string
export let query: DocumentQuery<Task>
export let options: FindOptions<Task> | undefined
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
// export let config: string[]
@@ -96,7 +94,8 @@
listProvider.updateSelection(evt.detail.docs, evt.detail.value)
}
const onContextMenu = (evt: any) => showMenu(evt.detail.evt, evt.detail.objects)
let resultQuery = { doneState: null, space }
$: resultQuery = { ...query, doneState: null }
</script>
{#await cardPresenter then presenter}
@@ -105,22 +104,13 @@
mode: 'browser'
}}
/>
<FilterBar
{_class}
query={{ doneState: null, space }}
on:change={(e) => {
resultQuery = e.detail
}}
/>
<KanbanUI
bind:this={kanbanUI}
{_class}
{search}
{options}
query={resultQuery}
{states}
fieldName={'state'}
rankFieldName={'rank'}
on:content={onContent}
on:obj-focus={onObjFocus}
checked={$selectionStore ?? []}
@@ -0,0 +1,33 @@
<!--
// 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 { Ref } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import type { DoneState } from '@hcengineering/task'
import task from '@hcengineering/task'
import DoneStatePresenter from './DoneStatePresenter.svelte'
export let value: Ref<DoneState>
export let showTitle: boolean = true
let state: DoneState | undefined
const query = createQuery()
$: query.query(task.class.DoneState, { _id: value }, (res) => ([state] = res), { limit: 1 })
</script>
{#if state}
<DoneStatePresenter value={state} {showTitle} />
{/if}
@@ -0,0 +1,31 @@
<!--
// 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 { Ref } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import task, { State } from '@hcengineering/task'
import StatePresenter from './StatePresenter.svelte'
export let value: Ref<State>
let state: State | undefined
const query = createQuery()
$: query.query(task.class.State, { _id: value }, (res) => ([state] = res), { limit: 1 })
</script>
{#if state}
<StatePresenter value={state} />
{/if}
+4
View File
@@ -39,6 +39,8 @@ import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte'
import Todos from './components/todos/Todos.svelte'
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
import Dashboard from './components/Dashboard.svelte'
import DoneStateRefPresenter from './components/state/DoneStateRefPresenter.svelte'
import StateRefPresenter from './components/state/StateRefPresenter.svelte'
export { default as AssigneePresenter } from './components/AssigneePresenter.svelte'
@@ -70,6 +72,8 @@ export default async (): Promise<Resources> => ({
KanbanTemplateEditor,
KanbanTemplateSelector,
AssignedTasks,
DoneStateRefPresenter,
StateRefPresenter,
TodoItemsPopup
},
actionImpl: {
+1
View File
@@ -9,6 +9,7 @@
"build:docs": "api-extractor run --local",
"lint": "svelte-check && eslint",
"lint:fix": "eslint --fix src",
"svelte-check": "svelte-check",
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src"
},
"devDependencies": {
@@ -15,7 +15,7 @@
<script lang="ts">
import contact, { Employee, EmployeeAccount, formatName } from '@hcengineering/contact'
import { getCurrentAccount } from '@hcengineering/core'
import login from '@hcengineering/login'
import login, { loginId } from '@hcengineering/login'
import { setMetadata } from '@hcengineering/platform'
import { Avatar, createQuery } from '@hcengineering/presentation'
import setting, { settingId, SettingsCategory } from '@hcengineering/setting'
@@ -68,11 +68,11 @@
setMetadata(login.metadata.LoginToken, null)
setMetadataLocalStorage(login.metadata.LoginEndpoint, null)
setMetadataLocalStorage(login.metadata.LoginEmail, null)
navigate({ path: [login.component.LoginApp] })
navigate({ path: [loginId] })
}
function selectWorkspace (): void {
navigate({ path: [login.component.LoginApp, 'selectWorkspace'] })
navigate({ path: [loginId, 'selectWorkspace'] })
}
function filterItems (items: SettingsCategory[]): SettingsCategory[] {
@@ -91,6 +91,7 @@
<div class="ap-scroll">
<div class="ap-box">
{#await getItems() then items}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="ap-menuItem flex-row-center"
on:click={() => {

Some files were not shown because too many files have changed in this diff Show More