mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-23 18:15:03 +02:00
Keyboard popup (#1625)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -94,9 +94,6 @@ function getId (d: Doc): Ref<Doc> {
|
||||
} else if (d._class === ('workbench:class:Application' as Ref<Doc>)) {
|
||||
const cr = d as any
|
||||
return ('workbench.app.' + (cr.label as string)) as Ref<Doc>
|
||||
} else if (d._class === ('view:class:ActionTarget' as Ref<Doc>)) {
|
||||
const cr = d as any
|
||||
return ((cr.target as string) + '.' + (cr.action as string)) as Ref<Doc>
|
||||
} else if (d._class === ('server-core:class:Trigger' as Ref<Doc>)) {
|
||||
const cr = d as any
|
||||
return cr.trigger as string as Ref<Doc>
|
||||
|
||||
@@ -37,7 +37,7 @@ import chunter from '@anticrm/model-chunter'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import core, { TAttachedDoc } from '@anticrm/model-core'
|
||||
import { TSpaceWithStates } from '@anticrm/model-task'
|
||||
import view from '@anticrm/model-view'
|
||||
import view, { createAction } from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import notification from '@anticrm/notification'
|
||||
import calendar from './plugin'
|
||||
@@ -153,25 +153,28 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: calendar.string.Calendar, visible: true },
|
||||
calendar.category.Calendar
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: calendar.actionImpl.SaveEventReminder,
|
||||
label: calendar.string.RemindMeAt,
|
||||
icon: calendar.icon.Reminder,
|
||||
action: calendar.actionImpl.SaveEventReminder,
|
||||
singleInput: true
|
||||
input: 'focus',
|
||||
category: calendar.category.Calendar,
|
||||
target: calendar.class.Event,
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
},
|
||||
calendar.action.SaveEventReminder
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: calendar.class.Event,
|
||||
action: calendar.action.SaveEventReminder,
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(calendar.mixin.Reminder, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: calendar.component.ReminderPresenter
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Ref } from '@anticrm/core'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import { Action, ViewAction, ViewletDescriptor } from '@anticrm/view'
|
||||
import { Action, ActionCategory, ViewAction, ViewletDescriptor } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(calendarId, calendar, {
|
||||
component: {
|
||||
@@ -32,6 +32,9 @@ export default mergeIds(calendarId, calendar, {
|
||||
action: {
|
||||
SaveEventReminder: '' as Ref<Action>
|
||||
},
|
||||
category: {
|
||||
Calendar: '' as Ref<ActionCategory>
|
||||
},
|
||||
actionImpl: {
|
||||
SaveEventReminder: '' as ViewAction
|
||||
},
|
||||
|
||||
+51
-58
@@ -20,10 +20,10 @@ import type {
|
||||
ChunterMessage,
|
||||
ChunterSpace,
|
||||
Comment,
|
||||
DirectMessage,
|
||||
Message,
|
||||
SavedMessages,
|
||||
ThreadMessage,
|
||||
DirectMessage
|
||||
ThreadMessage
|
||||
} from '@anticrm/chunter'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import type { Account, Class, Doc, Domain, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
@@ -43,11 +43,11 @@ import {
|
||||
} from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import core, { TAttachedDoc, TSpace } from '@anticrm/model-core'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import chunter from './plugin'
|
||||
import notification from '@anticrm/model-notification'
|
||||
import preference, { TPreference } from '@anticrm/model-preference'
|
||||
import view, { createAction } from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import chunter from './plugin'
|
||||
|
||||
export const DOMAIN_CHUNTER = 'chunter' as Domain
|
||||
export const DOMAIN_COMMENT = 'comment' as Domain
|
||||
@@ -204,87 +204,80 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: chunter.string.Chat, visible: true },
|
||||
chunter.category.Chunter
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
label: chunter.string.MarkUnread,
|
||||
action: chunter.actionImpl.MarkUnread,
|
||||
singleInput: true
|
||||
label: chunter.string.MarkUnread,
|
||||
input: 'focus',
|
||||
category: chunter.category.Chunter,
|
||||
target: chunter.class.Message,
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
},
|
||||
chunter.action.MarkUnread
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
label: chunter.string.MarkUnread,
|
||||
action: chunter.actionImpl.MarkCommentUnread,
|
||||
singleInput: true
|
||||
input: 'focus',
|
||||
category: chunter.category.Chunter,
|
||||
target: chunter.class.ThreadMessage,
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
},
|
||||
chunter.action.MarkCommentUnread
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: chunter.class.Message,
|
||||
action: chunter.action.MarkUnread,
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: chunter.actionImpl.ArchiveChannel,
|
||||
label: chunter.string.ArchiveChannel,
|
||||
icon: view.icon.Archive,
|
||||
action: chunter.actionImpl.ArchiveChannel
|
||||
input: 'focus',
|
||||
category: chunter.category.Chunter,
|
||||
target: chunter.class.Channel,
|
||||
query: {
|
||||
archived: false
|
||||
},
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
},
|
||||
chunter.action.ArchiveChannel
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: chunter.actionImpl.UnarchiveChannel,
|
||||
label: chunter.string.UnarchiveChannel,
|
||||
icon: view.icon.Archive,
|
||||
action: chunter.actionImpl.UnarchiveChannel
|
||||
input: 'focus',
|
||||
category: chunter.category.Chunter,
|
||||
target: chunter.class.Channel,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
},
|
||||
chunter.action.UnarchiveChannel
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: chunter.class.Channel,
|
||||
action: chunter.action.ArchiveChannel,
|
||||
query: {
|
||||
archived: false
|
||||
},
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: chunter.class.Channel,
|
||||
action: chunter.action.UnarchiveChannel,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: chunter.class.ThreadMessage,
|
||||
action: chunter.action.MarkCommentUnread,
|
||||
context: {
|
||||
mode: 'context'
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
core.space.Model,
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { Ref } from '@anticrm/core'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Action, ViewAction, ViewletDescriptor } from '@anticrm/view'
|
||||
import type { Action, ActionCategory, ViewAction, ViewletDescriptor } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(chunterId, chunter, {
|
||||
component: {
|
||||
@@ -43,6 +43,9 @@ export default mergeIds(chunterId, chunter, {
|
||||
ArchiveChannel: '' as ViewAction,
|
||||
UnarchiveChannel: '' as ViewAction
|
||||
},
|
||||
category: {
|
||||
Chunter: '' as Ref<ActionCategory>
|
||||
},
|
||||
string: {
|
||||
ApplicationLabelChunter: '' as IntlString,
|
||||
LeftComment: '' as IntlString,
|
||||
|
||||
@@ -31,7 +31,7 @@ import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import core, { TAccount, TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
|
||||
import presentation from '@anticrm/model-presentation'
|
||||
import view, { actionTarget } from '@anticrm/model-view'
|
||||
import view, { actionTemplates, createAction } from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import type { Asset, IntlString } from '@anticrm/platform'
|
||||
import contact from './plugin'
|
||||
@@ -290,7 +290,11 @@ export function createModel (builder: Builder): void {
|
||||
contact.completion.OrganizationCategory
|
||||
)
|
||||
|
||||
actionTarget(builder, view.action.Open, contact.class.Contact, { mode: ['browser', 'context'] })
|
||||
createAction(builder, {
|
||||
...actionTemplates.open,
|
||||
target: contact.class.Contact,
|
||||
context: { mode: ['browser', 'context'] }
|
||||
})
|
||||
}
|
||||
|
||||
export { contactOperation } from './migration'
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"@anticrm/inventory": "~0.6.0",
|
||||
"@anticrm/inventory-resources": "~0.6.0",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/workbench": "~0.6.1"
|
||||
"@anticrm/workbench": "~0.6.1",
|
||||
"@anticrm/model-view": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import workbench from '@anticrm/model-workbench'
|
||||
import type {} from '@anticrm/view'
|
||||
import view from '@anticrm/view'
|
||||
import inventory from './plugin'
|
||||
import { createAction } from '@anticrm/model-view'
|
||||
|
||||
export const DOMAIN_INVENTORY = 'inventory' as Domain
|
||||
@Model(inventory.class.Category, core.class.AttachedDoc, DOMAIN_INVENTORY)
|
||||
@@ -131,25 +132,24 @@ export function createModel (builder: Builder): void {
|
||||
inventory.app.Inventory
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: inventory.string.Inventory, visible: true },
|
||||
inventory.category.Inventory
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
label: inventory.string.CreateSubcategory,
|
||||
icon: inventory.icon.Categories,
|
||||
action: inventory.actionImpl.CreateSubcategory,
|
||||
input: 'focus',
|
||||
category: inventory.category.Inventory,
|
||||
target: inventory.class.Category,
|
||||
action: inventory.action.CreateSubcategory,
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
{
|
||||
label: inventory.string.CreateSubcategory,
|
||||
icon: inventory.icon.Categories,
|
||||
action: inventory.actionImpl.CreateSubcategory,
|
||||
singleInput: true
|
||||
},
|
||||
inventory.action.CreateSubcategory
|
||||
)
|
||||
}
|
||||
|
||||
export { default } from './plugin'
|
||||
|
||||
@@ -19,7 +19,7 @@ import { inventoryId } from '@anticrm/inventory'
|
||||
import inventory from '@anticrm/inventory-resources/src/plugin'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import { Action, ViewAction } from '@anticrm/view'
|
||||
import { Action, ActionCategory, ViewAction } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(inventoryId, inventory, {
|
||||
action: {
|
||||
@@ -28,6 +28,9 @@ export default mergeIds(inventoryId, inventory, {
|
||||
actionImpl: {
|
||||
CreateSubcategory: '' as ViewAction
|
||||
},
|
||||
category: {
|
||||
Inventory: '' as Ref<ActionCategory>
|
||||
},
|
||||
component: {
|
||||
Categories: '' as AnyComponent,
|
||||
Products: '' as AnyComponent,
|
||||
|
||||
+104
-88
@@ -36,10 +36,12 @@ import contact, { TPerson } from '@anticrm/model-contact'
|
||||
import core, { TSpace } from '@anticrm/model-core'
|
||||
import presentation from '@anticrm/model-presentation'
|
||||
import tags from '@anticrm/model-tags'
|
||||
import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
|
||||
import view, { actionTarget, createAction } from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import task, { TSpaceWithStates, TTask, actionTemplates } from '@anticrm/model-task'
|
||||
import view, { createAction, actionTemplates as viewTemplates } from '@anticrm/model-view'
|
||||
import workbench, { Application, createNavigateAction } from '@anticrm/model-workbench'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
|
||||
import { KeyBinding } from '@anticrm/view'
|
||||
import recruit from './plugin'
|
||||
import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review'
|
||||
import { TOpinion, TReview, TReviewCategory } from './review-model'
|
||||
@@ -139,6 +141,13 @@ export function createModel (builder: Builder): void {
|
||||
component: recruit.component.CreateCandidate
|
||||
})
|
||||
|
||||
const vacanciesId = 'vacancies'
|
||||
const candidatesId = 'candidates'
|
||||
const skillsId = 'skills'
|
||||
const applicantsId = 'applicants'
|
||||
const archiveId = 'archive'
|
||||
const assignedId = 'assigned'
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
core.space.Model,
|
||||
@@ -157,7 +166,7 @@ export function createModel (builder: Builder): void {
|
||||
],
|
||||
specials: [
|
||||
{
|
||||
id: 'vacancies',
|
||||
id: vacanciesId,
|
||||
component: recruit.component.Vacancies,
|
||||
icon: recruit.icon.Vacancy,
|
||||
label: recruit.string.Vacancies,
|
||||
@@ -165,7 +174,7 @@ export function createModel (builder: Builder): void {
|
||||
position: 'bottom'
|
||||
},
|
||||
{
|
||||
id: 'applicants',
|
||||
id: applicantsId,
|
||||
component: recruit.component.ApplicationsView,
|
||||
icon: recruit.icon.Application,
|
||||
label: recruit.string.Applications,
|
||||
@@ -173,7 +182,7 @@ export function createModel (builder: Builder): void {
|
||||
position: 'bottom'
|
||||
},
|
||||
{
|
||||
id: 'candidates',
|
||||
id: candidatesId,
|
||||
component: recruit.component.Candidates,
|
||||
icon: contact.icon.Person,
|
||||
label: recruit.string.Candidates,
|
||||
@@ -181,7 +190,7 @@ export function createModel (builder: Builder): void {
|
||||
position: 'bottom'
|
||||
},
|
||||
{
|
||||
id: 'archive',
|
||||
id: archiveId,
|
||||
component: workbench.component.Archive,
|
||||
icon: view.icon.Archive,
|
||||
label: workbench.string.Archive,
|
||||
@@ -190,7 +199,7 @@ export function createModel (builder: Builder): void {
|
||||
spaceClass: recruit.class.Vacancy
|
||||
},
|
||||
{
|
||||
id: 'skills',
|
||||
id: skillsId,
|
||||
component: recruit.component.SkillsView,
|
||||
icon: tags.icon.Tags,
|
||||
label: recruit.string.SkillsLabel,
|
||||
@@ -198,7 +207,7 @@ export function createModel (builder: Builder): void {
|
||||
position: 'bottom'
|
||||
},
|
||||
{
|
||||
id: 'assigned',
|
||||
id: assignedId,
|
||||
label: task.string.Assigned,
|
||||
icon: task.icon.Task,
|
||||
component: task.component.AssignedTasks,
|
||||
@@ -352,40 +361,46 @@ export function createModel (builder: Builder): void {
|
||||
validator: recruit.validator.ApplicantValidator
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
recruit.action.CreateApplication,
|
||||
recruit.string.CreateAnApplication,
|
||||
recruit.actionImpl.CreateApplication,
|
||||
{
|
||||
icon: recruit.icon.Create,
|
||||
singleInput: true
|
||||
}
|
||||
)
|
||||
actionTarget(builder, recruit.action.CreateApplication, contact.class.Person, { mode: ['context', 'browser'] })
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
recruit.action.CreateCandidate,
|
||||
recruit.string.CreateCandidate,
|
||||
recruit.actionImpl.CreateCandidate,
|
||||
{
|
||||
icon: recruit.icon.Create,
|
||||
keyBinding: ['c'],
|
||||
singleInput: true
|
||||
}
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: recruit.string.RecruitApplication, visible: true },
|
||||
recruit.category.Recruit
|
||||
)
|
||||
|
||||
actionTarget(builder, recruit.action.CreateCandidate, core.class.Doc, {
|
||||
mode: ['workbench', 'browser'],
|
||||
application: recruit.app.Recruit
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: recruit.component.CreateApplication,
|
||||
_id: 'candidate',
|
||||
element: 'top',
|
||||
props: {
|
||||
preserveCandidate: true
|
||||
}
|
||||
},
|
||||
label: recruit.string.CreateAnApplication,
|
||||
icon: recruit.icon.Create,
|
||||
input: 'focus',
|
||||
category: recruit.category.Recruit,
|
||||
target: contact.class.Person,
|
||||
context: { mode: ['context', 'browser'] }
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: recruit.mixin.Candidate,
|
||||
action: task.action.CreateTask,
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: recruit.component.CreateCandidate,
|
||||
element: 'top'
|
||||
},
|
||||
label: recruit.string.CreateCandidate,
|
||||
icon: recruit.icon.Create,
|
||||
keyBinding: ['c'],
|
||||
input: 'none',
|
||||
category: recruit.category.Recruit,
|
||||
target: core.class.Doc,
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
mode: ['workbench', 'browser'],
|
||||
application: recruit.app.Recruit
|
||||
}
|
||||
})
|
||||
|
||||
@@ -411,55 +426,21 @@ export function createModel (builder: Builder): void {
|
||||
recruit.completion.ApplicationCategory
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
createAction(builder, { ...actionTemplates.archiveSpace, target: recruit.class.Vacancy })
|
||||
createAction(builder, { ...actionTemplates.unarchiveSpace, target: recruit.class.Vacancy })
|
||||
|
||||
createAction(builder, {
|
||||
label: recruit.string.EditVacancy,
|
||||
icon: recruit.icon.Vacancy,
|
||||
action: view.actionImpl.ShowPanel,
|
||||
actionProps: {
|
||||
component: recruit.component.EditVacancy,
|
||||
element: 'right'
|
||||
},
|
||||
input: 'focus',
|
||||
category: recruit.category.Recruit,
|
||||
keyBinding: ['e'],
|
||||
target: recruit.class.Vacancy,
|
||||
action: task.action.ArchiveSpace,
|
||||
query: {
|
||||
archived: false
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: recruit.class.Vacancy,
|
||||
action: task.action.UnarchiveSpace,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: recruit.class.ReviewCategory,
|
||||
action: task.action.UnarchiveSpace,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
{
|
||||
label: recruit.string.EditVacancy,
|
||||
icon: recruit.icon.Vacancy,
|
||||
action: recruit.actionImpl.EditVacancy,
|
||||
singleInput: true
|
||||
},
|
||||
recruit.action.EditVacancy
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: recruit.class.Vacancy,
|
||||
action: recruit.action.EditVacancy,
|
||||
query: {},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
@@ -470,8 +451,43 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
createReviewModel(builder)
|
||||
|
||||
actionTarget(builder, view.action.Open, recruit.class.Vacancy, { mode: ['browser', 'context'] })
|
||||
actionTarget(builder, view.action.Open, recruit.class.Applicant, { mode: ['browser', 'context'] })
|
||||
// createAction(builder, { ...viewTemplates.open, target: recruit.class.Vacancy, context: { mode: ['browser', 'context'] } })
|
||||
createAction(builder, {
|
||||
...viewTemplates.open,
|
||||
target: recruit.class.Applicant,
|
||||
context: { mode: ['browser', 'context'] }
|
||||
})
|
||||
|
||||
function createGotoSpecialAction (builder: Builder, id: string, key: KeyBinding, label: IntlString): void {
|
||||
createNavigateAction(builder, key, label, {
|
||||
application: recruit.app.Recruit as Ref<Application>,
|
||||
mode: 'special',
|
||||
special: id
|
||||
})
|
||||
}
|
||||
|
||||
createGotoSpecialAction(builder, candidatesId, 'g->e', recruit.string.GotoCandidates)
|
||||
createGotoSpecialAction(builder, vacanciesId, 'g->v', recruit.string.GotoVacancies)
|
||||
createGotoSpecialAction(builder, skillsId, 'g->s', recruit.string.GotoSkills)
|
||||
createGotoSpecialAction(builder, assignedId, 'g->h', recruit.string.GotoAssigned)
|
||||
createGotoSpecialAction(builder, applicantsId, 'g->a', recruit.string.GotoApplicants)
|
||||
|
||||
createAction(builder, {
|
||||
action: workbench.actionImpl.Navigate,
|
||||
actionProps: {
|
||||
mode: 'app',
|
||||
application: recruit.app.Recruit as Ref<Application>,
|
||||
special: candidatesId
|
||||
},
|
||||
label: recruit.string.GotoRecruitApplication,
|
||||
icon: view.icon.ArrowRight,
|
||||
input: 'none',
|
||||
category: view.category.Navigation,
|
||||
target: core.class.Doc,
|
||||
context: {
|
||||
mode: ['workbench', 'browser', 'editor', 'panel', 'popup']
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export { recruitOperation } from './migration'
|
||||
|
||||
@@ -21,23 +21,18 @@ import { recruitId } from '@anticrm/recruit'
|
||||
import recruit from '@anticrm/recruit-resources/src/plugin'
|
||||
import { KanbanTemplate } from '@anticrm/task'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Action, ViewAction } from '@anticrm/view'
|
||||
import type { Action, ActionCategory, ViewAction } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(recruitId, recruit, {
|
||||
action: {
|
||||
CreateCandidate: '' as Ref<Action>,
|
||||
CreateApplication: '' as Ref<Action>,
|
||||
EditVacancy: '' as Ref<Action>,
|
||||
CreateReview: '' as Ref<Action>,
|
||||
CreateOpinion: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
CreateCandidate: '' as ViewAction,
|
||||
CreateApplication: '' as ViewAction,
|
||||
EditVacancy: '' as ViewAction,
|
||||
CreateReview: '' as ViewAction,
|
||||
CreateOpinion: '' as ViewAction
|
||||
},
|
||||
category: {
|
||||
Recruit: '' as Ref<ActionCategory>
|
||||
},
|
||||
string: {
|
||||
ApplicationShort: '' as IntlString,
|
||||
ApplicationsShort: '' as IntlString,
|
||||
@@ -50,7 +45,13 @@ export default mergeIds(recruitId, recruit, {
|
||||
Due: '' as IntlString,
|
||||
Source: '' as IntlString,
|
||||
ManageVacancyStatuses: '' as IntlString,
|
||||
EditVacancy: '' as IntlString
|
||||
EditVacancy: '' as IntlString,
|
||||
GotoCandidates: '' as IntlString,
|
||||
GotoVacancies: '' as IntlString,
|
||||
GotoSkills: '' as IntlString,
|
||||
GotoAssigned: '' as IntlString,
|
||||
GotoApplicants: '' as IntlString,
|
||||
GotoRecruitApplication: '' as IntlString
|
||||
},
|
||||
validator: {
|
||||
ApplicantValidator: '' as Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Builder } from '@anticrm/model'
|
||||
import calendar from '@anticrm/model-calendar'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import core from '@anticrm/model-core'
|
||||
import task from '@anticrm/model-task'
|
||||
import view from '@anticrm/model-view'
|
||||
import { actionTemplates } from '@anticrm/model-task'
|
||||
import view, { createAction } from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import { Review } from '@anticrm/recruit'
|
||||
import { BuildModelKey } from '@anticrm/view'
|
||||
@@ -49,26 +49,22 @@ export function createReviewModel (builder: Builder): void {
|
||||
|
||||
createTableViewlet(builder)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
label: recruit.string.CreateOpinion,
|
||||
icon: recruit.icon.Create,
|
||||
action: recruit.actionImpl.CreateOpinion,
|
||||
singleInput: true
|
||||
input: 'focus',
|
||||
category: recruit.category.Recruit,
|
||||
target: recruit.class.Review,
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
},
|
||||
recruit.action.CreateOpinion
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: recruit.class.Review,
|
||||
action: recruit.action.CreateOpinion,
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(recruit.class.Review, core.class.Class, view.mixin.ObjectEditor, {
|
||||
editor: recruit.component.EditReview
|
||||
})
|
||||
@@ -89,35 +85,29 @@ export function createReviewModel (builder: Builder): void {
|
||||
editor: recruit.component.EditReview
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
{
|
||||
label: recruit.string.CreateReview,
|
||||
icon: recruit.icon.Create,
|
||||
action: recruit.actionImpl.CreateReview,
|
||||
singleInput: true
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: recruit.component.CreateReview,
|
||||
_id: 'candidate',
|
||||
_space: 'space',
|
||||
element: 'top',
|
||||
props: {
|
||||
preserveCandidate: true
|
||||
}
|
||||
},
|
||||
recruit.action.CreateReview
|
||||
)
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: recruit.class.Applicant,
|
||||
action: recruit.action.CreateReview,
|
||||
label: recruit.string.CreateReview,
|
||||
icon: recruit.icon.Create,
|
||||
input: 'focus',
|
||||
category: recruit.category.Recruit,
|
||||
target: recruit.mixin.Candidate,
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: recruit.class.ReviewCategory,
|
||||
action: task.action.ArchiveSpace,
|
||||
query: {
|
||||
archived: false
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
})
|
||||
createAction(builder, { ...actionTemplates.archiveSpace, target: recruit.class.ReviewCategory })
|
||||
createAction(builder, { ...actionTemplates.unarchiveSpace, target: recruit.class.ReviewCategory })
|
||||
|
||||
const reviewOptions: FindOptions<Review> = {
|
||||
lookup: {
|
||||
|
||||
+94
-119
@@ -36,9 +36,9 @@ import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
|
||||
import presentation from '@anticrm/model-presentation'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import view, { actionTemplates as viewTemplates, createAction, template } from '@anticrm/model-view'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { ViewAction } from '@anticrm/view'
|
||||
import type {
|
||||
DoneState,
|
||||
DoneStateTemplate,
|
||||
@@ -59,10 +59,9 @@ import type {
|
||||
WonStateTemplate
|
||||
} from '@anticrm/task'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import type { ActionTarget } from '@anticrm/view'
|
||||
import task from './plugin'
|
||||
|
||||
export { taskOperation, createKanbanTemplate, createSequence } from './migration'
|
||||
export { createKanbanTemplate, createSequence, taskOperation } from './migration'
|
||||
export { default } from './plugin'
|
||||
|
||||
export const DOMAIN_TASK = 'task' as Domain
|
||||
@@ -240,6 +239,59 @@ export class TDocWithRank extends TDoc {
|
||||
rank!: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const actionTemplates = template({
|
||||
editStatus: {
|
||||
label: task.string.EditStates,
|
||||
icon: view.icon.Statuses,
|
||||
action: task.actionImpl.EditStatuses,
|
||||
input: 'focus',
|
||||
category: task.category.Task
|
||||
},
|
||||
archiveSpace: {
|
||||
label: task.string.Archive,
|
||||
icon: view.icon.Archive,
|
||||
action: view.actionImpl.UpdateDocument as ViewAction,
|
||||
actionProps: {
|
||||
key: 'archived',
|
||||
value: true,
|
||||
ask: true,
|
||||
label: task.string.Archive,
|
||||
message: task.string.ArchiveConfirm
|
||||
},
|
||||
input: 'focus',
|
||||
category: task.category.Task,
|
||||
query: {
|
||||
archived: false
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
},
|
||||
unarchiveSpace: {
|
||||
label: task.string.Unarchive,
|
||||
icon: view.icon.Archive,
|
||||
action: view.actionImpl.UpdateDocument as ViewAction,
|
||||
actionProps: {
|
||||
key: 'archived',
|
||||
ask: true,
|
||||
value: false,
|
||||
label: task.string.Unarchive,
|
||||
message: task.string.UnarchiveConfirm
|
||||
},
|
||||
input: 'focus',
|
||||
category: task.category.Task,
|
||||
query: {
|
||||
archived: true
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(
|
||||
TDocWithRank,
|
||||
@@ -262,13 +314,6 @@ export function createModel (builder: Builder): void {
|
||||
TIssue,
|
||||
TTodoItem
|
||||
)
|
||||
builder.mixin(task.class.Project, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
class: task.class.Issue,
|
||||
createItemDialog: task.component.CreateTask,
|
||||
createItemLabel: task.string.TaskCreateLabel
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
@@ -281,36 +326,6 @@ export function createModel (builder: Builder): void {
|
||||
task.viewlet.StatusTable
|
||||
)
|
||||
|
||||
// builder.createDoc(
|
||||
// workbench.class.Application,
|
||||
// core.space.Model,
|
||||
// {
|
||||
// label: task.string.ApplicationLabelTask,
|
||||
// icon: task.icon.Task,
|
||||
// hidden: false,
|
||||
// navigatorModel: {
|
||||
// spaces: [
|
||||
// {
|
||||
// label: task.string.Projects,
|
||||
// spaceClass: task.class.Project,
|
||||
// addSpaceLabel: task.string.CreateProject,
|
||||
// createComponent: task.component.CreateProject
|
||||
// }
|
||||
// ],
|
||||
// specials: [
|
||||
// {
|
||||
// id: 'assigned',
|
||||
// label: task.string.Assigned,
|
||||
// icon: task.icon.Task,
|
||||
// component: task.component.AssignedTasks,
|
||||
// position: 'top'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
// task.app.Tasks
|
||||
// )
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: task.class.Issue,
|
||||
descriptor: task.viewlet.StatusTable,
|
||||
@@ -385,62 +400,27 @@ export function createModel (builder: Builder): void {
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{
|
||||
label: task.string.CreateTask,
|
||||
icon: task.icon.Task,
|
||||
action: task.actionImpl.CreateTask,
|
||||
singleInput: true
|
||||
},
|
||||
task.action.CreateTask
|
||||
{ label: task.string.Task, visible: true },
|
||||
task.category.Task
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
label: task.string.EditStates,
|
||||
icon: view.icon.Statuses,
|
||||
action: task.actionImpl.EditStatuses,
|
||||
singleInput: true
|
||||
...actionTemplates.editStatus,
|
||||
target: task.class.SpaceWithStates,
|
||||
query: {
|
||||
archived: false
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
},
|
||||
task.action.EditStatuses
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
{
|
||||
label: task.string.Archive,
|
||||
icon: view.icon.Archive,
|
||||
action: task.actionImpl.ArchiveSpace
|
||||
},
|
||||
task.action.ArchiveSpace
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
{
|
||||
label: task.string.Unarchive,
|
||||
icon: view.icon.Archive,
|
||||
action: task.actionImpl.UnarchiveSpace
|
||||
},
|
||||
task.action.UnarchiveSpace
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: task.class.SpaceWithStates,
|
||||
action: task.action.EditStatuses,
|
||||
query: {
|
||||
archived: false
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(task.class.State, core.class.Class, view.mixin.AttributeEditor, {
|
||||
editor: task.component.StateEditor
|
||||
})
|
||||
@@ -476,47 +456,42 @@ export function createModel (builder: Builder): void {
|
||||
presenter: task.component.TodoItemPresenter
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
{
|
||||
label: task.string.MarkAsDone,
|
||||
icon: task.icon.TodoCheck,
|
||||
action: task.actionImpl.TodoItemMarkDone
|
||||
createAction(builder, {
|
||||
label: task.string.MarkAsDone,
|
||||
icon: task.icon.TodoCheck,
|
||||
action: view.actionImpl.UpdateDocument,
|
||||
actionProps: {
|
||||
key: 'done',
|
||||
value: true
|
||||
},
|
||||
task.action.TodoItemMarkDone
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
{
|
||||
label: task.string.MarkAsUndone,
|
||||
icon: task.icon.TodoUnCheck,
|
||||
action: task.actionImpl.TodoItemMarkUnDone
|
||||
},
|
||||
task.action.TodoItemMarkUnDone
|
||||
)
|
||||
|
||||
builder.createDoc<ActionTarget<TodoItem>>(view.class.ActionTarget, core.space.Model, {
|
||||
target: task.class.TodoItem,
|
||||
action: task.action.TodoItemMarkDone,
|
||||
input: 'focus',
|
||||
category: task.category.Task,
|
||||
query: {
|
||||
done: false
|
||||
},
|
||||
target: task.class.TodoItem,
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
})
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target: task.class.TodoItem,
|
||||
action: task.action.TodoItemMarkUnDone,
|
||||
|
||||
createAction(builder, {
|
||||
label: task.string.MarkAsUndone,
|
||||
icon: task.icon.TodoUnCheck,
|
||||
action: view.actionImpl.UpdateDocument,
|
||||
actionProps: {
|
||||
key: 'done',
|
||||
value: false
|
||||
},
|
||||
input: 'focus',
|
||||
category: task.category.Task,
|
||||
query: {
|
||||
done: true
|
||||
},
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
},
|
||||
target: task.class.TodoItem
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
@@ -530,9 +505,9 @@ export function createModel (builder: Builder): void {
|
||||
task.completion.IssueCategory
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
createAction(builder, {
|
||||
...viewTemplates.move,
|
||||
target: task.class.Task,
|
||||
action: view.action.Move,
|
||||
context: {
|
||||
mode: ['context', 'browser']
|
||||
}
|
||||
|
||||
@@ -21,29 +21,27 @@ import { mergeIds } from '@anticrm/platform'
|
||||
import { KanbanTemplate, taskId } from '@anticrm/task'
|
||||
import task from '@anticrm/task-resources/src/plugin'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Action, ViewAction } from '@anticrm/view'
|
||||
import type { Action, ActionCategory, ViewAction } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(taskId, task, {
|
||||
action: {
|
||||
CreateTask: '' as Ref<Action>,
|
||||
EditStatuses: '' as Ref<Action>,
|
||||
TodoItemMarkDone: '' as Ref<Action>,
|
||||
TodoItemMarkUnDone: '' as Ref<Action>,
|
||||
ArchiveSpace: '' as Ref<Action>,
|
||||
UnarchiveSpace: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
CreateTask: '' as ViewAction,
|
||||
EditStatuses: '' as ViewAction,
|
||||
TodoItemMarkDone: '' as ViewAction,
|
||||
TodoItemMarkUnDone: '' as ViewAction,
|
||||
ArchiveSpace: '' as ViewAction,
|
||||
UnarchiveSpace: '' as ViewAction
|
||||
},
|
||||
category: {
|
||||
Task: '' as Ref<ActionCategory>
|
||||
},
|
||||
component: {
|
||||
ProjectView: '' as AnyComponent,
|
||||
CreateProject: '' as AnyComponent,
|
||||
CreateTask: '' as AnyComponent,
|
||||
EditIssue: '' as AnyComponent,
|
||||
TaskPresenter: '' as AnyComponent,
|
||||
KanbanCard: '' as AnyComponent,
|
||||
|
||||
@@ -34,18 +34,20 @@ import {
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import core, { DOMAIN_SPACE, TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
|
||||
import { createAction } from '@anticrm/model-view'
|
||||
import workbench, { createNavigateAction } from '@anticrm/model-workbench'
|
||||
import { Asset, IntlString } from '@anticrm/platform'
|
||||
import view, { KeyBinding } from '@anticrm/view'
|
||||
import {
|
||||
Document,
|
||||
Issue,
|
||||
IssuePriority,
|
||||
IssueStatus,
|
||||
Project,
|
||||
IssueStatusCategory,
|
||||
Project,
|
||||
ProjectStatus,
|
||||
Team
|
||||
} from '@anticrm/tracker'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import { Asset, IntlString } from '@anticrm/platform'
|
||||
import tracker from './plugin'
|
||||
|
||||
export { trackerOperation } from './migration'
|
||||
@@ -292,6 +294,12 @@ export function createModel (builder: Builder): void {
|
||||
tracker.issueStatusCategory.Canceled
|
||||
)
|
||||
|
||||
const issuesId = 'issues'
|
||||
const activeId = 'active'
|
||||
const backlogId = 'backlog'
|
||||
const boardId = 'board'
|
||||
const projectsId = 'projects'
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
core.space.Model,
|
||||
@@ -332,31 +340,31 @@ export function createModel (builder: Builder): void {
|
||||
icon: tracker.icon.Home,
|
||||
specials: [
|
||||
{
|
||||
id: 'issues',
|
||||
id: issuesId,
|
||||
label: tracker.string.Issues,
|
||||
icon: tracker.icon.Issues,
|
||||
component: tracker.component.Issues
|
||||
},
|
||||
{
|
||||
id: 'active',
|
||||
id: activeId,
|
||||
label: tracker.string.Active,
|
||||
// icon: tracker.icon.TrackerApplication,
|
||||
component: tracker.component.Active
|
||||
},
|
||||
{
|
||||
id: 'backlog',
|
||||
id: backlogId,
|
||||
label: tracker.string.Backlog,
|
||||
// icon: tracker.icon.TrackerApplication,
|
||||
component: tracker.component.Backlog
|
||||
},
|
||||
{
|
||||
id: 'board',
|
||||
id: boardId,
|
||||
label: tracker.string.Board,
|
||||
// icon: tracker.icon.TrackerApplication,
|
||||
component: tracker.component.Board
|
||||
},
|
||||
{
|
||||
id: 'projects',
|
||||
id: projectsId,
|
||||
label: tracker.string.Projects,
|
||||
icon: tracker.icon.Projects,
|
||||
component: tracker.component.Projects
|
||||
@@ -369,4 +377,35 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
tracker.app.Tracker
|
||||
)
|
||||
|
||||
function createGotoSpecialAction (builder: Builder, id: string, key: KeyBinding, label: IntlString): void {
|
||||
createNavigateAction(builder, key, label, {
|
||||
application: tracker.app.Tracker,
|
||||
mode: 'space',
|
||||
spaceSpecial: id,
|
||||
spaceClass: tracker.class.Team
|
||||
})
|
||||
}
|
||||
|
||||
createGotoSpecialAction(builder, issuesId, 'g->e', tracker.string.GotoIssues)
|
||||
createGotoSpecialAction(builder, activeId, 'g->a', tracker.string.GotoActive)
|
||||
createGotoSpecialAction(builder, backlogId, 'g->b', tracker.string.GotoBacklog)
|
||||
createGotoSpecialAction(builder, boardId, 'g->d', tracker.string.GotoBoard)
|
||||
createGotoSpecialAction(builder, projectsId, 'g->p', tracker.string.GotoProjects)
|
||||
|
||||
createAction(builder, {
|
||||
action: workbench.actionImpl.Navigate,
|
||||
actionProps: {
|
||||
mode: 'app',
|
||||
application: tracker.app.Tracker
|
||||
},
|
||||
label: tracker.string.GotoTrackerApplication,
|
||||
icon: view.icon.ArrowRight,
|
||||
input: 'none',
|
||||
category: view.category.Navigation,
|
||||
target: core.class.Doc,
|
||||
context: {
|
||||
mode: ['workbench', 'browser', 'editor', 'panel', 'popup']
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,7 +24,13 @@ import { Application } from '@anticrm/workbench'
|
||||
export default mergeIds(trackerId, tracker, {
|
||||
string: {
|
||||
TrackerApplication: '' as IntlString,
|
||||
Teams: '' as IntlString
|
||||
Teams: '' as IntlString,
|
||||
GotoIssues: '' as IntlString,
|
||||
GotoActive: '' as IntlString,
|
||||
GotoBacklog: '' as IntlString,
|
||||
GotoBoard: '' as IntlString,
|
||||
GotoProjects: '' as IntlString,
|
||||
GotoTrackerApplication: '' as IntlString
|
||||
},
|
||||
team: {
|
||||
DefaultTeam: '' as Ref<Team>
|
||||
|
||||
+220
-113
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Class, Client, Doc, DocumentQuery, Ref, Space } from '@anticrm/core'
|
||||
import type { Class, Client, Data, Doc, DocumentQuery, Ref, Space } from '@anticrm/core'
|
||||
import { DOMAIN_MODEL } from '@anticrm/core'
|
||||
import { Builder, Mixin, Model } from '@anticrm/model'
|
||||
import core, { TClass, TDoc } from '@anticrm/model-core'
|
||||
@@ -21,7 +21,7 @@ import type { Asset, IntlString, Resource, Status } from '@anticrm/platform'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type {
|
||||
Action,
|
||||
ActionTarget,
|
||||
ActionCategory,
|
||||
AttributeEditor,
|
||||
AttributePresenter,
|
||||
HTMLPresenter,
|
||||
@@ -37,8 +37,8 @@ import type {
|
||||
SpaceName,
|
||||
TextPresenter,
|
||||
ViewAction,
|
||||
ViewActionInput,
|
||||
ViewContext,
|
||||
ViewContextType,
|
||||
Viewlet,
|
||||
ViewletDescriptor
|
||||
} from '@anticrm/view'
|
||||
@@ -47,52 +47,13 @@ import view from './plugin'
|
||||
export { viewOperation } from './migration'
|
||||
export { ViewAction }
|
||||
|
||||
export function createAction (
|
||||
export function createAction<T extends Doc = Doc, P = Record<string, any>> (
|
||||
builder: Builder,
|
||||
id: Ref<Action>,
|
||||
label: IntlString,
|
||||
action: ViewAction,
|
||||
config?: {
|
||||
icon?: Asset
|
||||
keyBinding?: KeyBinding[]
|
||||
singleInput?: boolean
|
||||
}
|
||||
data: Data<Action<T, P>>,
|
||||
id?: Ref<Action>
|
||||
): void {
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
{
|
||||
label,
|
||||
icon: config?.icon,
|
||||
keyBinding: config?.keyBinding,
|
||||
singleInput: config?.singleInput,
|
||||
action
|
||||
},
|
||||
id
|
||||
)
|
||||
}
|
||||
|
||||
export function actionTarget (
|
||||
builder: Builder,
|
||||
action: Ref<Action>,
|
||||
target: Ref<Class<Doc>>,
|
||||
options: {
|
||||
mode: ViewContextType | ViewContextType[]
|
||||
application?: Ref<Doc>
|
||||
group?: string
|
||||
override?: ViewAction
|
||||
}
|
||||
): void {
|
||||
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||
target,
|
||||
action,
|
||||
context: {
|
||||
mode: options.mode,
|
||||
application: options.application,
|
||||
group: options.group
|
||||
},
|
||||
override: options.override
|
||||
})
|
||||
const { label, ...adata } = data as Data<Action>
|
||||
builder.createDoc(view.class.Action, core.space.Model, { label, ...adata }, id)
|
||||
}
|
||||
|
||||
export function classPresenter (
|
||||
@@ -168,16 +129,27 @@ export class TViewlet extends TDoc implements Viewlet {
|
||||
@Model(view.class.Action, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TAction extends TDoc implements Action {
|
||||
label!: IntlString
|
||||
icon?: Asset
|
||||
icon!: Asset
|
||||
|
||||
action!: ViewAction
|
||||
actionProps!: Record<string, any>
|
||||
input!: ViewActionInput
|
||||
|
||||
target!: Ref<Class<Doc>>
|
||||
query!: DocumentQuery<Doc>
|
||||
|
||||
inputProps!: Record<string, Ref<Class<Doc>>>
|
||||
keyBinding!: KeyBinding[]
|
||||
description!: IntlString
|
||||
category!: Ref<ActionCategory>
|
||||
context!: ViewContext
|
||||
}
|
||||
|
||||
@Model(view.class.ActionTarget, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TActionTarget extends TDoc implements ActionTarget {
|
||||
target!: Ref<Class<Doc>>
|
||||
action!: Ref<Action>
|
||||
query!: DocumentQuery<Doc>
|
||||
context!: ViewContext
|
||||
@Model(view.class.ActionCategory, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TActionCategory extends TDoc implements ActionCategory {
|
||||
label!: IntlString
|
||||
icon?: Asset
|
||||
visible!: boolean
|
||||
}
|
||||
|
||||
@Mixin(view.mixin.IgnoreActions, core.class.Class)
|
||||
@@ -207,6 +179,33 @@ export class TLinkPresenter extends TDoc implements LinkPresenter {
|
||||
component!: AnyComponent
|
||||
}
|
||||
|
||||
export type ActionTemplate = Partial<Data<Action>>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function template<N extends Record<string, ActionTemplate>> (t: N): N {
|
||||
return t
|
||||
}
|
||||
|
||||
export const actionTemplates = template({
|
||||
move: {
|
||||
label: view.string.Move,
|
||||
action: view.actionImpl.Move,
|
||||
icon: view.icon.Move,
|
||||
input: 'focus',
|
||||
category: view.category.General
|
||||
},
|
||||
open: {
|
||||
action: view.actionImpl.Open,
|
||||
label: view.string.Open,
|
||||
icon: view.icon.Open,
|
||||
keyBinding: ['Enter'],
|
||||
input: 'focus',
|
||||
category: view.category.General
|
||||
}
|
||||
})
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(
|
||||
TAttributeEditor,
|
||||
@@ -215,7 +214,7 @@ export function createModel (builder: Builder): void {
|
||||
TViewletDescriptor,
|
||||
TViewlet,
|
||||
TAction,
|
||||
TActionTarget,
|
||||
TActionCategory,
|
||||
TObjectValidator,
|
||||
TObjectFactory,
|
||||
TObjectEditorHeader,
|
||||
@@ -237,6 +236,37 @@ export function createModel (builder: Builder): void {
|
||||
classPresenter(builder, core.class.TypeDate, view.component.DatePresenter, view.component.DateEditor)
|
||||
classPresenter(builder, core.class.Space, view.component.ObjectPresenter)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: view.string.General, visible: true },
|
||||
view.category.General
|
||||
)
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: view.string.General, visible: false },
|
||||
view.category.GeneralNavigation
|
||||
)
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: view.string.Editor, visible: false },
|
||||
view.category.Editor
|
||||
)
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: view.string.Navigation, visible: true },
|
||||
view.category.Navigation
|
||||
)
|
||||
builder.createDoc(
|
||||
view.class.ActionCategory,
|
||||
core.space.Model,
|
||||
{ label: view.string.MarkdownFormatting, visible: false },
|
||||
view.category.MarkdownFormatting
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
@@ -248,74 +278,154 @@ export function createModel (builder: Builder): void {
|
||||
view.viewlet.Table
|
||||
)
|
||||
|
||||
createAction(builder, view.action.Delete, view.string.Delete, view.actionImpl.Delete, {
|
||||
icon: view.icon.Delete,
|
||||
keyBinding: ['Meta + Backspace', 'Ctrl + Backspace']
|
||||
})
|
||||
actionTarget(builder, view.action.Delete, core.class.Doc, { mode: ['context', 'browser'], group: 'tools' })
|
||||
|
||||
createAction(builder, view.action.Move, view.string.Move, view.actionImpl.Move, {
|
||||
icon: view.icon.Move,
|
||||
singleInput: true
|
||||
})
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.Delete,
|
||||
label: view.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
keyBinding: ['Meta + Backspace', 'Ctrl + Backspace'],
|
||||
category: view.category.General,
|
||||
input: 'any',
|
||||
target: core.class.Doc,
|
||||
context: { mode: ['context', 'browser'], group: 'tools' }
|
||||
},
|
||||
view.action.Delete
|
||||
)
|
||||
|
||||
// Keyboard actions.
|
||||
createAction(builder, view.action.MoveUp, view.string.MoveUp, view.actionImpl.MoveUp, {
|
||||
keyBinding: ['ArrowUp', 'keyK']
|
||||
})
|
||||
actionTarget(builder, view.action.MoveUp, core.class.Doc, { mode: 'browser' })
|
||||
createAction(builder, view.action.MoveDown, view.string.MoveDown, view.actionImpl.MoveDown, {
|
||||
keyBinding: ['ArrowDown', 'keyJ']
|
||||
})
|
||||
actionTarget(builder, view.action.MoveDown, core.class.Doc, { mode: 'browser' })
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.MoveUp,
|
||||
label: view.string.MoveUp,
|
||||
keyBinding: ['ArrowUp', 'keyK'],
|
||||
category: view.category.GeneralNavigation,
|
||||
input: 'none',
|
||||
target: core.class.Doc,
|
||||
context: { mode: 'browser' }
|
||||
},
|
||||
view.action.MoveUp
|
||||
)
|
||||
|
||||
createAction(builder, view.action.MoveLeft, view.string.MoveLeft, view.actionImpl.MoveLeft, {
|
||||
keyBinding: ['ArrowLeft']
|
||||
})
|
||||
actionTarget(builder, view.action.MoveLeft, core.class.Doc, { mode: 'browser' })
|
||||
createAction(builder, view.action.MoveRight, view.string.MoveRight, view.actionImpl.MoveRight, {
|
||||
keyBinding: ['ArrowRight']
|
||||
})
|
||||
actionTarget(builder, view.action.MoveRight, core.class.Doc, { mode: 'browser' })
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.MoveDown,
|
||||
label: view.string.MoveDown,
|
||||
keyBinding: ['ArrowDown', 'keyJ'],
|
||||
category: view.category.GeneralNavigation,
|
||||
input: 'none',
|
||||
target: core.class.Doc,
|
||||
context: { mode: 'browser' }
|
||||
},
|
||||
view.action.MoveDown
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.MoveLeft,
|
||||
label: view.string.MoveLeft,
|
||||
keyBinding: ['ArrowLeft'],
|
||||
category: view.category.GeneralNavigation,
|
||||
input: 'none',
|
||||
target: core.class.Doc,
|
||||
context: { mode: 'browser' }
|
||||
},
|
||||
view.action.MoveLeft
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.MoveRight,
|
||||
label: view.string.MoveRight,
|
||||
keyBinding: ['ArrowRight'],
|
||||
category: view.category.GeneralNavigation,
|
||||
input: 'none',
|
||||
target: core.class.Doc,
|
||||
context: { mode: 'browser' }
|
||||
},
|
||||
view.action.MoveRight
|
||||
)
|
||||
|
||||
builder.mixin(core.class.Space, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: view.component.SpacePresenter
|
||||
})
|
||||
|
||||
// Selection stuff
|
||||
createAction(builder, view.action.SelectItem, view.string.SelectItem, view.actionImpl.SelectItem, {
|
||||
keyBinding: ['keyX']
|
||||
})
|
||||
actionTarget(builder, view.action.SelectItem, core.class.Doc, { mode: 'browser' })
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
label: view.string.SelectItem,
|
||||
action: view.actionImpl.SelectItem,
|
||||
keyBinding: ['keyX'],
|
||||
category: view.category.General,
|
||||
input: 'focus',
|
||||
target: core.class.Doc,
|
||||
context: { mode: 'browser' }
|
||||
},
|
||||
view.action.SelectItem
|
||||
)
|
||||
|
||||
createAction(builder, view.action.SelectItemAll, view.string.SelectItemAll, view.actionImpl.SelectItemAll, {
|
||||
keyBinding: ['meta + keyA', 'ctrl + keyA']
|
||||
})
|
||||
actionTarget(builder, view.action.SelectItemAll, core.class.Doc, { mode: 'browser' })
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
label: view.string.SelectItemAll,
|
||||
action: view.actionImpl.SelectItemAll,
|
||||
keyBinding: ['meta + keyA', 'ctrl + keyA'],
|
||||
category: view.category.General,
|
||||
input: 'none',
|
||||
target: core.class.Doc,
|
||||
context: { mode: 'browser' }
|
||||
},
|
||||
view.action.SelectItemAll
|
||||
)
|
||||
|
||||
createAction(builder, view.action.SelectItemNone, view.string.SelectItemNone, view.actionImpl.SelectItemNone, {
|
||||
keyBinding: ['escape']
|
||||
})
|
||||
actionTarget(builder, view.action.SelectItemNone, core.class.Doc, { mode: 'browser' })
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.SelectItemNone,
|
||||
label: view.string.SelectItemNone,
|
||||
keyBinding: ['escape'],
|
||||
category: view.category.General,
|
||||
input: 'selection',
|
||||
target: core.class.Doc,
|
||||
context: { mode: 'browser' }
|
||||
},
|
||||
view.action.SelectItemNone
|
||||
)
|
||||
|
||||
createAction(builder, view.action.ShowActions, view.string.ShowActions, view.actionImpl.ShowActions, {
|
||||
keyBinding: ['meta + keyK', 'ctrl + keyK']
|
||||
})
|
||||
actionTarget(builder, view.action.ShowActions, core.class.Doc, {
|
||||
mode: ['workbench', 'browser', 'popup', 'panel', 'editor']
|
||||
})
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowActions,
|
||||
label: view.string.ShowActions,
|
||||
keyBinding: ['meta + keyK', 'ctrl + keyK'],
|
||||
category: view.category.GeneralNavigation,
|
||||
input: 'none',
|
||||
target: core.class.Doc,
|
||||
context: {
|
||||
mode: ['workbench', 'browser', 'popup', 'panel', 'editor', 'input']
|
||||
}
|
||||
},
|
||||
view.action.ShowActions
|
||||
)
|
||||
|
||||
createAction(builder, view.action.ShowPreview, view.string.ShowPreview, view.actionImpl.ShowPreview, {
|
||||
keyBinding: ['Space'],
|
||||
singleInput: true
|
||||
})
|
||||
actionTarget(builder, view.action.ShowPreview, core.class.Doc, { mode: 'browser' })
|
||||
|
||||
createAction(builder, view.action.Open, view.string.Open, view.actionImpl.Open, {
|
||||
icon: view.icon.Open,
|
||||
keyBinding: ['Enter'],
|
||||
singleInput: true
|
||||
})
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPreview,
|
||||
label: view.string.ShowPreview,
|
||||
keyBinding: ['Space'],
|
||||
input: 'focus',
|
||||
category: view.category.General,
|
||||
target: core.class.Doc,
|
||||
context: { mode: 'browser' }
|
||||
},
|
||||
view.action.ShowPreview
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.LinkPresenter, core.space.Model, {
|
||||
pattern: '(www.)?youtube.(com|ru)',
|
||||
@@ -326,9 +436,6 @@ export function createModel (builder: Builder): void {
|
||||
pattern: '(www.)?github.com/',
|
||||
component: view.component.GithubPresenter
|
||||
})
|
||||
|
||||
// Should be contributed via individual plugins.
|
||||
// actionTarget(builder, view.action.Open, core.class.Doc, { mode: ['browser', 'context'] })
|
||||
}
|
||||
|
||||
export default view
|
||||
|
||||
@@ -92,6 +92,11 @@ export default mergeIds(viewId, view, {
|
||||
SelectDown: '' as IntlString,
|
||||
ShowPreview: '' as IntlString,
|
||||
ShowActions: '' as IntlString,
|
||||
Open: '' as IntlString
|
||||
Open: '' as IntlString,
|
||||
// Action categories
|
||||
General: '' as IntlString,
|
||||
Navigation: '' as IntlString,
|
||||
Editor: '' as IntlString,
|
||||
MarkdownFormatting: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
"@anticrm/model-core": "~0.6.0",
|
||||
"@anticrm/workbench": "~0.6.1",
|
||||
"@anticrm/ui": "~0.6.0",
|
||||
"@anticrm/view": "~0.6.0"
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/model-view": "~0.6.0",
|
||||
"@anticrm/workbench-resources": "~0.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,17 @@
|
||||
//
|
||||
|
||||
import type { IntlString, Asset } from '@anticrm/platform'
|
||||
import { DOMAIN_MODEL } from '@anticrm/core'
|
||||
import { Class, DOMAIN_MODEL, Ref, Space } from '@anticrm/core'
|
||||
import { Model, Mixin, Builder, UX } from '@anticrm/model'
|
||||
import type { Application, SpaceView, ViewConfiguration } from '@anticrm/workbench'
|
||||
import view from '@anticrm/view'
|
||||
import view, { KeyBinding } from '@anticrm/view'
|
||||
import { createAction } from '@anticrm/model-view'
|
||||
|
||||
import core, { TDoc, TClass } from '@anticrm/model-core'
|
||||
import workbench from './plugin'
|
||||
|
||||
export { Application }
|
||||
|
||||
@Model(workbench.class.Application, core.class.Doc, DOMAIN_MODEL)
|
||||
@UX(workbench.string.Application)
|
||||
export class TApplication extends TDoc implements Application {
|
||||
@@ -43,3 +46,32 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
|
||||
export default workbench
|
||||
|
||||
export function createNavigateAction (
|
||||
builder: Builder,
|
||||
key: KeyBinding,
|
||||
label: IntlString,
|
||||
config: {
|
||||
mode: 'app' | 'special' | 'space'
|
||||
application: Ref<Application>
|
||||
special?: string
|
||||
space?: Ref<Space>
|
||||
spaceClass?: Ref<Class<Space>>
|
||||
spaceSpecial?: string
|
||||
}
|
||||
): void {
|
||||
createAction(builder, {
|
||||
action: workbench.actionImpl.Navigate,
|
||||
actionProps: config,
|
||||
label,
|
||||
icon: view.icon.ArrowRight,
|
||||
keyBinding: [key],
|
||||
input: 'none',
|
||||
category: view.category.Navigation,
|
||||
target: core.class.Doc,
|
||||
context: {
|
||||
mode: ['workbench', 'browser', 'editor', 'panel', 'popup'],
|
||||
application: config.application
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
import { Space } from '@anticrm/core'
|
||||
import { IntlString, mergeIds, Resource } from '@anticrm/platform'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import workbench, { workbenchId } from '@anticrm/workbench'
|
||||
import { workbenchId } from '@anticrm/workbench'
|
||||
import workbench from '@anticrm/workbench-resources/src/plugin'
|
||||
|
||||
export default mergeIds(workbenchId, workbench, {
|
||||
component: {
|
||||
@@ -25,7 +26,6 @@ export default mergeIds(workbenchId, workbench, {
|
||||
SpaceBrowser: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Archive: '' as IntlString,
|
||||
Application: '' as IntlString,
|
||||
SpaceBrowser: '' as IntlString
|
||||
},
|
||||
|
||||
@@ -75,7 +75,7 @@ function identify (result: Record<string, any>, prefix: string, namespace: Recor
|
||||
for (const key in namespace) {
|
||||
const value = namespace[key]
|
||||
if (typeof result[key] === 'string') {
|
||||
throw new Error(`'identify' overwrites '${key}'.`)
|
||||
throw new Error(`'identify' overwrites '${key}' for ${prefix}`)
|
||||
}
|
||||
const ident = prefix + _ID_SEPARATOR + key
|
||||
result[key] = typeof value === 'string' ? ident : identify(result[key] ?? {}, ident, value)
|
||||
|
||||
@@ -523,6 +523,10 @@ a.no-line {
|
||||
.text-xs { font-size: .625rem; }
|
||||
.text-sm { font-size: .75rem; }
|
||||
.text-md { font-size: .8125rem; }
|
||||
.text-base {
|
||||
font-size: 1rem; /* 16px */
|
||||
line-height: 1.5rem; /* 24px */
|
||||
}
|
||||
.text-lg { font-size: 1.125rem; }
|
||||
.font-normal { font-weight: 400; }
|
||||
.font-medium { font-weight: 500; }
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
max-height: calc(100vh - 2rem);
|
||||
height: auto;
|
||||
}
|
||||
&.width-40 {
|
||||
max-width: 40rem !important;
|
||||
width: 40rem !important;
|
||||
}
|
||||
.header {
|
||||
border-bottom: 1px solid var(--popup-divider);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export type {
|
||||
AnySvelteComponentWithProps,
|
||||
Location,
|
||||
PopupAlignment,
|
||||
PopupPositionElement,
|
||||
ButtonKind,
|
||||
ButtonSize
|
||||
} from './types'
|
||||
|
||||
@@ -72,16 +72,9 @@ export interface PopupPositionElement {
|
||||
h: HorizontalAlignment
|
||||
}
|
||||
}
|
||||
export type PopupAlignment =
|
||||
| PopupPositionElement
|
||||
| null
|
||||
| 'right'
|
||||
| 'top'
|
||||
| 'float'
|
||||
| 'account'
|
||||
| 'full'
|
||||
| 'content'
|
||||
| 'middle'
|
||||
export type PopupPosAlignment = 'right' | 'top' | 'float' | 'account' | 'full' | 'content' | 'middle'
|
||||
export type PopupAlignment = PopupPosAlignment | PopupPositionElement | null
|
||||
|
||||
export type TooltipAlignment = 'top' | 'bottom' | 'left' | 'right'
|
||||
export type VerticalAlignment = 'top' | 'bottom'
|
||||
export type HorizontalAlignment = 'left' | 'right'
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
"VacancyCreateLabel": "Vacancy",
|
||||
"VacancyPlaceholder": "Software Engineer",
|
||||
"MakePrivateDescription": "Only members can see it",
|
||||
"CreateAnApplication": "Create an application",
|
||||
"CreateAnApplication": "Create an Application",
|
||||
"NoApplicationsForCandidate": "There are no applications for this candidate.",
|
||||
"CreateApplication": "Create Application",
|
||||
"CreateApplication": "Create an Application",
|
||||
"ApplicationCreateLabel": "Application",
|
||||
"SelectVacancy": "Select vacancy",
|
||||
"Candidate": "Candidate",
|
||||
"CandidateCreateLabel": "Candidate",
|
||||
"CreateCandidate": "Create Candidate",
|
||||
"CreateCandidate": "Create an Candidate",
|
||||
"AssignRecruiter": "Assign recruiter",
|
||||
"UnAssignRecruiter": "Unassign recruiter",
|
||||
"Recruiters": "Recruiters",
|
||||
@@ -56,13 +56,13 @@
|
||||
"ManageVacancyStatuses": "Manage vacancy statuses",
|
||||
"EditVacancy": "Edit",
|
||||
"FullDescription": "Full description",
|
||||
"CreateReviewCategory": "Create review category",
|
||||
"CreateReviewCategory": "Create Review category",
|
||||
"ReviewCategoryName": "Review category title*",
|
||||
"ReviewCategoryPlaceholder": "Interview",
|
||||
"ReviewCategory": "Reviews",
|
||||
"ReviewCategoryDescription": "Description",
|
||||
"ThisReviewCategoryIsPrivate": "This category is private",
|
||||
"CreateReview": "Schedule",
|
||||
"CreateReview": "Schedule an Review",
|
||||
"CreateReviewParams": "Schedule {label}",
|
||||
"SelectReviewCategory": "select category",
|
||||
"Reviews": "Reviews",
|
||||
@@ -87,7 +87,13 @@
|
||||
"NoParticipants": "No participants added",
|
||||
"PersonsLabel": "{name}",
|
||||
"AddDescription": "Add description",
|
||||
"NumberSkills": "{count, plural, =0 {no skills} =1 {1 skill} other {# skills}}"
|
||||
"NumberSkills": "{count, plural, =0 {no skills} =1 {1 skill} other {# skills}}",
|
||||
"GotoCandidates": "Go to Candidates",
|
||||
"GotoVacancies": "Go to Vacancies",
|
||||
"GotoSkills": "Go to Skills",
|
||||
"GotoAssigned": "Go to my Assigned",
|
||||
"GotoApplicants": "Go to Applications",
|
||||
"GotoRecruitApplication": "Switch to Recruit Application"
|
||||
},
|
||||
"status": {
|
||||
"CandidateRequired": "Please select candidate",
|
||||
|
||||
@@ -88,7 +88,14 @@
|
||||
"NoParticipants": "Участники не добавлены",
|
||||
"PersonsLabel": "{name}",
|
||||
"AddDescription": "Add description",
|
||||
"NumberSkills": "{count, plural, =0 {нет навыков} =1 {1 навык} =2 {2 навыка} other {# навыков}}"
|
||||
"NumberSkills": "{count, plural, =0 {нет навыков} =1 {1 навык} =2 {2 навыка} other {# навыков}}",
|
||||
|
||||
"GotoCandidates": "Перейте к кандидатам",
|
||||
"GotoVacancies": "Перейти к вакансиям",
|
||||
"GotoSkills": "Перейти к навыкам",
|
||||
"GotoAssigned": "Перейти к моим назначениям",
|
||||
"GotoApplicants": "Перейти к претендентам",
|
||||
"GotoRecruitApplication": "Перейти к Приложению Рекрутинг"
|
||||
},
|
||||
"status": {
|
||||
"CandidateRequired": "Пожалуйста выберите кандидата",
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
export function canClose (): boolean {
|
||||
return candidate === undefined && assignee === undefined
|
||||
return (preserveCandidate || candidate === undefined) && assignee === undefined
|
||||
}
|
||||
|
||||
async function createApplication () {
|
||||
@@ -179,23 +179,6 @@
|
||||
}}
|
||||
>
|
||||
<StatusControl slot="error" {status} />
|
||||
<!-- <div class="flex-between mt-2 mb-2">
|
||||
<div class="card" class:empty={!selectedCandidate}>
|
||||
{#if selectedCandidate}
|
||||
<CandidateCard candidate={selectedCandidate} disabled />
|
||||
{:else}
|
||||
<Label label={recruit.status.CandidateRequired} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="arrows"><ExpandRightDouble /></div>
|
||||
<div class="card" class:empty={!selectedVacancy}>
|
||||
{#if selectedVacancy}
|
||||
<VacancyCard vacancy={selectedVacancy} disabled />
|
||||
{:else}
|
||||
<Label label={recruit.status.VacancyRequired} />
|
||||
{/if}
|
||||
</div>
|
||||
</div> -->
|
||||
<svelte:fragment slot="pool">
|
||||
{#if !preserveCandidate}
|
||||
<UserBox
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Vacancy } from '@anticrm/recruit'
|
||||
import { Button, getCurrentLocation, Icon, IconAdd, Label, navigate, Scroller, showPopup } from '@anticrm/ui'
|
||||
import SearchEdit from '@anticrm/ui/src/components/SearchEdit.svelte'
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
import { SearchEdit } from '@anticrm/ui'
|
||||
import { TableBrowser } from '@anticrm/view-resources'
|
||||
import recruit from '../plugin'
|
||||
import CreateVacancy from './CreateVacancy.svelte'
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<Button icon={IconAdd} label={recruit.string.VacancyCreateLabel} kind={'primary'} on:click={showCreateDialog} />
|
||||
</div>
|
||||
<Scroller tableFade>
|
||||
<Table
|
||||
<TableBrowser
|
||||
_class={recruit.class.Vacancy}
|
||||
config={[
|
||||
{
|
||||
@@ -128,6 +128,5 @@
|
||||
archived: false
|
||||
}}
|
||||
showNotification
|
||||
highlightRows
|
||||
/>
|
||||
</Scroller>
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
export function canClose (): boolean {
|
||||
return (!preserveCandidate ? candidate === undefined : true) && title.length === 0
|
||||
return (preserveCandidate || candidate === undefined) && title.length === 0
|
||||
}
|
||||
|
||||
let spaceLabel: string = ''
|
||||
|
||||
@@ -18,11 +18,12 @@ import { IntlString, OK, Resources, Severity, Status, translate } from '@anticrm
|
||||
import { ObjectSearchResult } from '@anticrm/presentation'
|
||||
import { Applicant, Review } from '@anticrm/recruit'
|
||||
import task from '@anticrm/task'
|
||||
import { showPanel, showPopup } from '@anticrm/ui'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import ApplicationItem from './components/ApplicationItem.svelte'
|
||||
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
|
||||
import Applications from './components/Applications.svelte'
|
||||
import ApplicationsPresenter from './components/ApplicationsPresenter.svelte'
|
||||
import ApplicationsView from './components/ApplicationsView.svelte'
|
||||
import Candidates from './components/Candidates.svelte'
|
||||
import CreateApplication from './components/CreateApplication.svelte'
|
||||
import CreateCandidate from './components/CreateCandidate.svelte'
|
||||
@@ -30,48 +31,30 @@ import CreateVacancy from './components/CreateVacancy.svelte'
|
||||
import EditApplication from './components/EditApplication.svelte'
|
||||
import EditVacancy from './components/EditVacancy.svelte'
|
||||
import KanbanCard from './components/KanbanCard.svelte'
|
||||
import CreateReview from './components/review/CreateReview.svelte'
|
||||
import CreateOpinion from './components/review/CreateOpinion.svelte'
|
||||
import CreateReview from './components/review/CreateReview.svelte'
|
||||
import CreateReviewCategory from './components/review/CreateReviewCategory.svelte'
|
||||
import EditReview from './components/review/EditReview.svelte'
|
||||
import EditReviewCategory from './components/review/EditReviewCategory.svelte'
|
||||
import OpinionPresenter from './components/review/OpinionPresenter.svelte'
|
||||
import OpinionsPresenter from './components/review/OpinionsPresenter.svelte'
|
||||
import Opinions from './components/review/Opinions.svelte'
|
||||
import OpinionsPresenter from './components/review/OpinionsPresenter.svelte'
|
||||
import ReviewCategoryPresenter from './components/review/ReviewCategoryPresenter.svelte'
|
||||
import ReviewPresenter from './components/review/ReviewPresenter.svelte'
|
||||
import Reviews from './components/review/Reviews.svelte'
|
||||
import SkillsView from './components/SkillsView.svelte'
|
||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||
import Vacancies from './components/Vacancies.svelte'
|
||||
import VacancyItemPresenter from './components/VacancyItemPresenter.svelte'
|
||||
import VacancyPresenter from './components/VacancyPresenter.svelte'
|
||||
import VacancyCountPresenter from './components/VacancyCountPresenter.svelte'
|
||||
import VacancyItemPresenter from './components/VacancyItemPresenter.svelte'
|
||||
import VacancyModifiedPresenter from './components/VacancyModifiedPresenter.svelte'
|
||||
import ReviewCategoryPresenter from './components/review/ReviewCategoryPresenter.svelte'
|
||||
import ApplicationsView from './components/ApplicationsView.svelte'
|
||||
import VacancyPresenter from './components/VacancyPresenter.svelte'
|
||||
import recruit from './plugin'
|
||||
|
||||
async function createApplication (object: Doc): Promise<void> {
|
||||
showPopup(CreateApplication, { candidate: object._id, preserveCandidate: true })
|
||||
}
|
||||
|
||||
async function editVacancy (object: Doc): Promise<void> {
|
||||
showPanel(recruit.component.EditVacancy, object._id, object._class, 'right')
|
||||
}
|
||||
|
||||
async function createOpinion (object: Doc): Promise<void> {
|
||||
showPopup(CreateOpinion, { space: object.space, review: object._id })
|
||||
}
|
||||
|
||||
async function createReview (object: Doc): Promise<void> {
|
||||
showPopup(CreateReview, { application: object._id, preserveApplication: true })
|
||||
}
|
||||
|
||||
async function createCandidate (object: Doc | undefined, evt: Event): Promise<void> {
|
||||
evt.preventDefault()
|
||||
showPopup(CreateCandidate, {})
|
||||
}
|
||||
|
||||
export async function applicantValidator (applicant: Applicant, client: Client): Promise<Status> {
|
||||
if (applicant.attachedTo === undefined) {
|
||||
return new Status(Severity.INFO, recruit.status.CandidateRequired, {})
|
||||
@@ -143,11 +126,7 @@ export async function queryApplication (client: Client, search: string): Promise
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
actionImpl: {
|
||||
CreateApplication: createApplication,
|
||||
EditVacancy: editVacancy,
|
||||
CreateReview: createReview,
|
||||
CreateOpinion: createOpinion,
|
||||
CreateCandidate: createCandidate
|
||||
CreateOpinion: createOpinion
|
||||
},
|
||||
validator: {
|
||||
ApplicantValidator: applicantValidator,
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"StateTemplateColor": "Color",
|
||||
"KanbanTemplateTitle": "Title",
|
||||
"Rank": "Rank",
|
||||
"CreateTask": "Create task",
|
||||
"TaskCreateLabel": "Task",
|
||||
"EditStates": "Edit states",
|
||||
"Archive": "Archive",
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"StateTemplateColor": "Цвет",
|
||||
"KanbanTemplateTitle": "Заголовок",
|
||||
"Rank": "Ранк",
|
||||
"CreateTask": "Создать задачу",
|
||||
"TaskCreateLabel": "Задача",
|
||||
"EditStates": "Редактировать статусы",
|
||||
"Archive": "Архивировать",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import tags, { selectedTagElements, TagCategory, TagElement } from '@anticrm/tags'
|
||||
import { DoneState, Task } from '@anticrm/task'
|
||||
import { Component, Icon, Label, Scroller, SearchEdit } from '@anticrm/ui'
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
import { TableBrowser } from '@anticrm/view-resources'
|
||||
import task from '../plugin'
|
||||
|
||||
export let _class: Ref<Class<Task>> = task.class.Task
|
||||
@@ -100,7 +100,7 @@
|
||||
/>
|
||||
|
||||
<Scroller tableFade>
|
||||
<Table
|
||||
<TableBrowser
|
||||
{_class}
|
||||
config={[
|
||||
'',
|
||||
@@ -125,6 +125,5 @@
|
||||
options={taskOptions}
|
||||
query={resultQuery}
|
||||
showNotification
|
||||
highlightRows
|
||||
/>
|
||||
</Scroller>
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
<!--
|
||||
// Copyright © 2020 Anticrm Platform Contributors.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import { AttachedData, Data, Doc, Ref, SortingOrder, Space } from '@anticrm/core'
|
||||
import { generateId } from '@anticrm/core'
|
||||
import { OK, Status } from '@anticrm/platform'
|
||||
import { Card, getClient, UserBox } from '@anticrm/presentation'
|
||||
import task, { calcRank, Issue, State } from '@anticrm/task'
|
||||
import { EditBox, Grid, Status as StatusControl } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../plugin'
|
||||
|
||||
export let space: Ref<Space>
|
||||
export let parent: Pick<Doc, '_id' | '_class'> | undefined
|
||||
|
||||
let _space = space
|
||||
|
||||
$: _space = space
|
||||
const status: Status = OK
|
||||
|
||||
let assignee: Ref<Employee> | null = null
|
||||
|
||||
const object: Data<Issue> = {
|
||||
name: '',
|
||||
description: '',
|
||||
assignee: null,
|
||||
number: 0,
|
||||
attachedTo: task.global.Task,
|
||||
attachedToClass: task.class.Issue,
|
||||
collection: 'tasks',
|
||||
state: '' as Ref<State>,
|
||||
doneState: null,
|
||||
rank: ''
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const taskId: Ref<Issue> = generateId()
|
||||
|
||||
export function canClose (): boolean {
|
||||
return object.name !== ''
|
||||
}
|
||||
|
||||
async function createTask () {
|
||||
const state = await client.findOne(task.class.State, { space: _space })
|
||||
if (state === undefined) {
|
||||
throw new Error('create task: state not found')
|
||||
}
|
||||
|
||||
const sequence = await client.findOne(task.class.Sequence, { attachedTo: task.class.Issue })
|
||||
if (sequence === undefined) {
|
||||
throw new Error('sequence object not found')
|
||||
}
|
||||
|
||||
const lastOne = await client.findOne(
|
||||
task.class.Task,
|
||||
{ state: state._id },
|
||||
{ sort: { rank: SortingOrder.Descending } }
|
||||
)
|
||||
const incResult = await client.updateDoc(
|
||||
task.class.Sequence,
|
||||
task.space.Sequence,
|
||||
sequence._id,
|
||||
{
|
||||
$inc: { sequence: 1 }
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
const value: AttachedData<Issue> = {
|
||||
name: object.name,
|
||||
description: object.description,
|
||||
assignee,
|
||||
number: (incResult as any).object.sequence,
|
||||
doneState: null,
|
||||
state: state._id,
|
||||
rank: calcRank(lastOne, undefined)
|
||||
}
|
||||
|
||||
await client.addCollection(
|
||||
task.class.Issue,
|
||||
_space,
|
||||
parent?._id ?? task.global.Task,
|
||||
parent?._class ?? task.class.Issue,
|
||||
'tasks',
|
||||
value,
|
||||
taskId
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={plugin.string.CreateTask}
|
||||
okAction={createTask}
|
||||
canSave={object.name.length > 0 && _space != null}
|
||||
spaceClass={task.class.Project}
|
||||
spaceLabel={plugin.string.ProjectName}
|
||||
spacePlaceholder={plugin.string.SelectProject}
|
||||
bind:space={_space}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<StatusControl slot="error" {status} />
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<EditBox
|
||||
label={plugin.string.TaskName}
|
||||
bind:value={object.name}
|
||||
icon={task.icon.Task}
|
||||
placeholder={plugin.string.TaskNamePlaceholder}
|
||||
maxWidth={'16rem'}
|
||||
focus
|
||||
/>
|
||||
<UserBox
|
||||
_class={contact.class.Employee}
|
||||
label={plugin.string.TaskAssignee}
|
||||
placeholder={plugin.string.AssignThisTask}
|
||||
bind:value={assignee}
|
||||
allowDeselect
|
||||
titleDeselect={plugin.string.TaskUnAssign}
|
||||
/>
|
||||
</Grid>
|
||||
</Card>
|
||||
@@ -72,7 +72,6 @@
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
let kanbanUI: KanbanUI
|
||||
let objects: Doc[] = []
|
||||
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
|
||||
kanbanUI.select(offset, of, dir)
|
||||
})
|
||||
@@ -114,7 +113,6 @@
|
||||
rankFieldName={'rank'}
|
||||
on:content={(evt) => {
|
||||
listProvider.update(evt.detail)
|
||||
objects = evt.detail
|
||||
}}
|
||||
on:obj-focus={(evt) => {
|
||||
listProvider.updateFocus(evt.detail)
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Class, Client, Doc, Ref } from '@anticrm/core'
|
||||
import { Class, Client, Ref } from '@anticrm/core'
|
||||
import { IntlString, Resources, translate } from '@anticrm/platform'
|
||||
import { getClient, MessageBox, ObjectSearchResult } from '@anticrm/presentation'
|
||||
import { SpaceWithStates, Task, TodoItem } from '@anticrm/task'
|
||||
import { ObjectSearchResult } from '@anticrm/presentation'
|
||||
import { SpaceWithStates, Task } from '@anticrm/task'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import CreateProject from './components/CreateProject.svelte'
|
||||
import CreateTask from './components/CreateTask.svelte'
|
||||
import EditIssue from './components/EditIssue.svelte'
|
||||
import KanbanTemplateEditor from './components/kanban/KanbanTemplateEditor.svelte'
|
||||
import KanbanTemplateSelector from './components/kanban/KanbanTemplateSelector.svelte'
|
||||
@@ -43,56 +42,10 @@ import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
|
||||
import AssignedTasks from './components/AssignedTasks.svelte'
|
||||
import task from './plugin'
|
||||
|
||||
async function createTask (object: Doc): Promise<void> {
|
||||
showPopup(CreateTask, { parent: object._id, space: object.space })
|
||||
}
|
||||
|
||||
async function editStatuses (object: SpaceWithStates): Promise<void> {
|
||||
showPopup(EditStatuses, { _id: object._id, spaceClass: object._class }, 'right')
|
||||
}
|
||||
|
||||
async function toggleDone (value: boolean, object: TodoItem): Promise<void> {
|
||||
await getClient().update(object, { done: value })
|
||||
}
|
||||
|
||||
async function ArchiveSpace (object: SpaceWithStates): Promise<void> {
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
label: task.string.Archive,
|
||||
message: task.string.ArchiveConfirm
|
||||
},
|
||||
undefined,
|
||||
(result: boolean) => {
|
||||
if (result) {
|
||||
const client = getClient()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
client.update(object, { archived: true })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function UnarchiveSpace (object: SpaceWithStates): Promise<void> {
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
label: task.string.Unarchive,
|
||||
message: task.string.UnarchiveConfirm
|
||||
},
|
||||
undefined,
|
||||
(result: boolean) => {
|
||||
if (result) {
|
||||
const client = getClient()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
client.update(object, { archived: false })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export async function queryTask<D extends Task> (
|
||||
_class: Ref<Class<D>>,
|
||||
client: Client,
|
||||
@@ -136,7 +89,6 @@ export type StatesBarPosition = 'start' | 'middle' | 'end' | undefined
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
CreateTask,
|
||||
CreateProject,
|
||||
TaskPresenter,
|
||||
EditIssue,
|
||||
@@ -158,12 +110,7 @@ export default async (): Promise<Resources> => ({
|
||||
TodoItemsPopup
|
||||
},
|
||||
actionImpl: {
|
||||
CreateTask: createTask,
|
||||
EditStatuses: editStatuses,
|
||||
TodoItemMarkDone: async (obj: TodoItem) => await toggleDone(true, obj),
|
||||
TodoItemMarkUnDone: async (obj: TodoItem) => await toggleDone(false, obj),
|
||||
ArchiveSpace,
|
||||
UnarchiveSpace
|
||||
EditStatuses: editStatuses
|
||||
},
|
||||
completion: {
|
||||
IssueQuery: async (client: Client, query: string) => await queryTask(task.class.Issue, client, query)
|
||||
|
||||
@@ -23,7 +23,6 @@ export default mergeIds(taskId, task, {
|
||||
CreateProject: '' as IntlString,
|
||||
ProjectName: '' as IntlString,
|
||||
ProjectNamePlaceholder: '' as IntlString,
|
||||
CreateTask: '' as IntlString,
|
||||
TaskCreateLabel: '' as IntlString,
|
||||
TaskAssignee: '' as IntlString,
|
||||
TaskNamePlaceholder: '' as IntlString,
|
||||
|
||||
@@ -94,7 +94,14 @@
|
||||
"PastWeek": "Past week",
|
||||
"PastMonth": "Past month",
|
||||
"CopyIssueUrl": "Copy Issue URL to clipboard",
|
||||
"CopyIssueId": "Copy Issue ID to clipboard"
|
||||
"CopyIssueId": "Copy Issue ID to clipboard",
|
||||
|
||||
"GotoIssues": "Go to issues",
|
||||
"GotoActive": "Go to active issues",
|
||||
"GotoBacklog": "Go to backlog",
|
||||
"GotoBoard": "Go to issue board",
|
||||
"GotoProjects": "Go to projects",
|
||||
"GotoTrackerApplication": "Switch to Tracker Application"
|
||||
},
|
||||
"status": {}
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"string": {
|
||||
"TrackerApplication": "Tracker",
|
||||
"Teams": "Your teams",
|
||||
"More": "More",
|
||||
"Delete": "Delete",
|
||||
"Open": "Open",
|
||||
"Members": "Members",
|
||||
"Inbox": "Inbox",
|
||||
"MyIssues": "My issues",
|
||||
"Issues": "Issues",
|
||||
"Views": "Views",
|
||||
"Active": "Active",
|
||||
"Backlog": "Backlog",
|
||||
"Board": "Board",
|
||||
"TrackerApplication": "Трекер",
|
||||
"Teams": "Команды",
|
||||
"More": "Больше",
|
||||
"Delete": "Удалить",
|
||||
"Open": "Открыть",
|
||||
"Members": "Участиники",
|
||||
"Inbox": "Входящие",
|
||||
"MyIssues": "Мои задачи",
|
||||
"Issues": "Задачи",
|
||||
"Views": "Отображения",
|
||||
"Active": "Активные",
|
||||
"Backlog": "Пул задач",
|
||||
"Board": "Канбан",
|
||||
"Projects": "Проекты",
|
||||
"NewProject": "Новый проект",
|
||||
"CreateProject": "Создать проект",
|
||||
@@ -66,7 +66,14 @@
|
||||
"DocumentColor": "",
|
||||
"Rank": "",
|
||||
"IssueTitlePlaceholder": "Имя задачи",
|
||||
"IssueDescriptionPlaceholder": "Описание задачи"
|
||||
"IssueDescriptionPlaceholder": "Описание задачи",
|
||||
|
||||
"GotoIssues": "Перейти к задачам",
|
||||
"GotoActive": "Перейти к активным задачам",
|
||||
"GotoBacklog": "Перейти к пулу задач",
|
||||
"GotoBoard": "Перейти к канбану",
|
||||
"GotoProjects": "Перейти к проекту",
|
||||
"GotoTrackerApplication": "Go to Tracker Application"
|
||||
},
|
||||
"status": {}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,9 @@
|
||||
<path d="M11.4,7.1C11.4,7.1,11.4,7.1,11.4,7.1c1.2-1.6,1.3-1.6,1.3-1.6C12.9,5,13,4.5,12.9,4c-0.1-0.5-0.4-0.9-0.8-1.2 c0,0-1.1-0.9-1.1-0.9c-0.8-0.7-2.1-0.6-2.8,0.3c0,0,0,0,0,0l-6.3,7.9c-0.3,0.4-0.4,0.9-0.3,1.4l0.5,2.3c0.1,0.2,0.3,0.4,0.5,0.4 c0,0,0,0,0,0l2.4,0c0.5,0,1-0.2,1.3-0.6C8.9,10.2,10.5,8.2,11.4,7.1C11.4,7.1,11.4,7.1,11.4,7.1z M8.9,2.8c0.3-0.4,1-0.5,1.4-0.1 c0,0,1.2,0.9,1.2,0.9c0.2,0.1,0.4,0.3,0.4,0.6c0.1,0.2,0,0.5-0.1,0.7c0,0-0.4,0.5-0.9,1.2L8.1,3.9L8.9,2.8z M5.5,12.9 C5.4,13,5.2,13.1,5,13.1l-2,0l-0.5-1.9c0-0.2,0-0.4,0.1-0.5l4.8-6l2.8,2.2C8.9,8.6,6.8,11.2,5.5,12.9z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id='arrow-right' viewBox="0 0 16 16">
|
||||
<path d="M13.334 8L13.6875 7.64645L14.0411 8L13.6875 8.35355L13.334 8ZM3.33398 8.5C3.05784 8.5 2.83398 8.27614 2.83398 8C2.83398 7.72386 3.05784 7.5 3.33398 7.5V8.5ZM9.68754 3.64645L13.6875 7.64645L12.9804 8.35355L8.98043 4.35355L9.68754 3.64645ZM13.6875 8.35355L9.68754 12.3536L8.98043 11.6464L12.9804 7.64645L13.6875 8.35355ZM13.334 8.5H3.33398V7.5H13.334V8.5Z"/>
|
||||
</symbol>
|
||||
|
||||
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.6 KiB |
@@ -15,6 +15,23 @@
|
||||
"DeleteObjectConfirm": "Do you want to delete this {count, plural, =1 {object} other {# objects}}?",
|
||||
"Open": "Open",
|
||||
"Assignees": "Assignees",
|
||||
"Labels": "Labels"
|
||||
"Labels": "Labels",
|
||||
"MoveLeft": "Move left",
|
||||
"MoveRight": "Move right",
|
||||
"MoveUp": "Move up",
|
||||
"MoveDown": "Move down",
|
||||
|
||||
"SelectItem": "Select focused item",
|
||||
"SelectItemAll": "Select all items",
|
||||
"SelectItemNone": "Deselect all items",
|
||||
|
||||
"ShowPreview": "Show document preview",
|
||||
"ShowActions": "Show actions popup",
|
||||
|
||||
"ActionPlaceholder": "type to filter...",
|
||||
"General": "General",
|
||||
"Navigation": "Navigation",
|
||||
"Editor": "Editor",
|
||||
"MarkdownFormatting": "Formatting"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
"DeleteObjectConfirm": "Вы действительно хотите удалить {count, plural, =1 {этот обьект} other {эти # обьекта}}?",
|
||||
"Open": "Открыть",
|
||||
"Assignees": "Исполнители",
|
||||
"Labels": "Метки"
|
||||
"Labels": "Метки",
|
||||
"ActionPlaceholder": "введите для филтрации...",
|
||||
"General": "Общее",
|
||||
"Navigation": "Навигация",
|
||||
"Editor": "Редактор",
|
||||
"MarkdownFormatting": "Форматирование"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ loadMetadata(view.icon, {
|
||||
MoreH: `${icons}#more-h`,
|
||||
Archive: `${icons}#archive`,
|
||||
Statuses: `${icons}#statuses`,
|
||||
Open: `${icons}#open`
|
||||
Open: `${icons}#open`,
|
||||
ArrowRight: `${icons}#arrow-right`
|
||||
})
|
||||
|
||||
addStringsLoader(viewId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Doc, Hierarchy } from '@anticrm/core'
|
||||
import { getClient, MessageBox } from '@anticrm/presentation'
|
||||
import { showPanel, showPopup } from '@anticrm/ui'
|
||||
import { AnyComponent, closeTooltip, PopupPositionElement, showPanel, showPopup } from '@anticrm/ui'
|
||||
import { ViewContext } from '@anticrm/view'
|
||||
import MoveView from './components/Move.svelte'
|
||||
import { contextStore } from './context'
|
||||
import view from './plugin'
|
||||
import { FocusSelection, focusStore, SelectDirection, selectionStore, previewDocument } from './selection'
|
||||
import { FocusSelection, focusStore, previewDocument, SelectDirection, selectionStore } from './selection'
|
||||
import { deleteObject } from './utils'
|
||||
|
||||
function Delete (object: Doc): void {
|
||||
@@ -31,12 +33,17 @@ async function Move (object: Doc): Promise<void> {
|
||||
}
|
||||
|
||||
let $focusStore: FocusSelection
|
||||
|
||||
focusStore.subscribe((it) => {
|
||||
$focusStore = it
|
||||
})
|
||||
|
||||
let $contextStore: ViewContext[]
|
||||
contextStore.subscribe((it) => {
|
||||
$contextStore = it
|
||||
})
|
||||
|
||||
export function select (evt: Event | undefined, offset: 1 | -1 | 0, of?: Doc, direction?: SelectDirection): void {
|
||||
closeTooltip()
|
||||
if ($focusStore.provider?.select !== undefined) {
|
||||
$focusStore.provider?.select(offset, of, direction)
|
||||
evt?.preventDefault()
|
||||
@@ -81,6 +88,8 @@ const MoveRight = (doc: Doc | undefined, evt: Event): void => select(evt, 1, doc
|
||||
|
||||
function ShowActions (doc: Doc | Doc[] | undefined, evt: Event): void {
|
||||
evt.preventDefault()
|
||||
|
||||
showPopup(view.component.ActionsPopup, { viewContext: $contextStore[$contextStore.length - 1] }, 'top')
|
||||
}
|
||||
|
||||
function ShowPreview (doc: Doc | undefined, evt: Event): void {
|
||||
@@ -98,6 +107,111 @@ function Open (doc: Doc, evt: Event): void {
|
||||
showPanel(view.component.EditDoc, doc._id, Hierarchy.mixinOrClass(doc), 'content')
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick action for show panel
|
||||
* Require props:
|
||||
* - component - view.component.EditDoc or another component
|
||||
* - element - position
|
||||
* - right - some right component
|
||||
*/
|
||||
function ShowPanel (
|
||||
doc: Doc | Doc[],
|
||||
evt: Event,
|
||||
props: {
|
||||
component?: AnyComponent
|
||||
element: PopupPositionElement
|
||||
rightSection?: AnyComponent
|
||||
}
|
||||
): void {
|
||||
if (Array.isArray(doc)) {
|
||||
console.error('Wrong show Panel parameters')
|
||||
return
|
||||
}
|
||||
evt.preventDefault()
|
||||
showPanel(
|
||||
props.component ?? view.component.EditDoc,
|
||||
doc._id,
|
||||
Hierarchy.mixinOrClass(doc),
|
||||
props.element ?? 'content',
|
||||
props.rightSection
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick action for show popup
|
||||
* Props:
|
||||
* - _id - object id will be placed into
|
||||
* - _class - object _class will be placed into
|
||||
* - value - object itself will be placed into
|
||||
* - values - all docs will be placed into
|
||||
* - props - some basic props, will be merged with key, _class, value, values
|
||||
*/
|
||||
function ShowPopup (
|
||||
doc: Doc | Doc[],
|
||||
evt: Event,
|
||||
props: {
|
||||
component: AnyComponent
|
||||
element: PopupPositionElement
|
||||
_id?: string
|
||||
_class?: string
|
||||
_space?: string
|
||||
value?: string
|
||||
values?: string
|
||||
props?: Record<string, any>
|
||||
}
|
||||
): void {
|
||||
const docs = Array.isArray(doc) ? doc : [doc]
|
||||
evt.preventDefault()
|
||||
let cprops = {
|
||||
...(props?.props ?? {})
|
||||
}
|
||||
if (docs.length > 0) {
|
||||
cprops = {
|
||||
...cprops,
|
||||
...{
|
||||
[props._id ?? '_id']: docs[0]._id,
|
||||
[props._class ?? '_class']: docs[0]._class,
|
||||
[props._space ?? 'space']: docs[0].space,
|
||||
[props.value ?? 'value']: docs[0],
|
||||
[props.values ?? 'values']: docs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showPopup(props.component, cprops, props.element)
|
||||
}
|
||||
|
||||
function UpdateDocument (doc: Doc | Doc[], evt: Event, props: Record<string, any>): void {
|
||||
async function update (): Promise<void> {
|
||||
if (props?.key !== undefined && props?.value !== undefined) {
|
||||
if (Array.isArray(doc)) {
|
||||
for (const d of doc) {
|
||||
await getClient().update(d, { [props.key]: props.value })
|
||||
}
|
||||
} else {
|
||||
await getClient().update(doc, { [props.key]: props.value })
|
||||
}
|
||||
}
|
||||
}
|
||||
if (props?.ask === true) {
|
||||
showPopup(
|
||||
MessageBox,
|
||||
{
|
||||
label: props.label ?? view.string.LabelYes,
|
||||
message: props.message ?? view.string.LabelYes
|
||||
},
|
||||
undefined,
|
||||
(result: boolean) => {
|
||||
if (result) {
|
||||
void update()
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
void update()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@@ -113,5 +227,8 @@ export const actionImpl = {
|
||||
SelectItemAll,
|
||||
ShowActions,
|
||||
ShowPreview,
|
||||
Open
|
||||
Open,
|
||||
UpdateDocument,
|
||||
ShowPanel,
|
||||
ShowPopup
|
||||
}
|
||||
|
||||
@@ -14,10 +14,24 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Doc } from '@anticrm/core'
|
||||
import core, { Class, Client, FindResult, matchQuery, Ref } from '@anticrm/core'
|
||||
import type { Action, ActionTarget, ViewContext } from '@anticrm/view'
|
||||
import type { Doc, WithLookup } from '@anticrm/core'
|
||||
import core, { Class, Client, matchQuery, Ref } from '@anticrm/core'
|
||||
import type { Action, ViewActionInput, ViewContextType } from '@anticrm/view'
|
||||
import view from './plugin'
|
||||
import { FocusSelection } from './selection'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function getSelection (focusStore: FocusSelection, selectionStore: Doc[]): Doc[] {
|
||||
let docs: Doc[] = []
|
||||
if (selectionStore.find((it) => it._id === focusStore.focus?._id) === undefined && focusStore.focus !== undefined) {
|
||||
docs = [focusStore.focus]
|
||||
} else {
|
||||
docs = selectionStore
|
||||
}
|
||||
return docs
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -31,77 +45,86 @@ export async function getActions (
|
||||
client: Client,
|
||||
doc: Doc | Doc[],
|
||||
derived: Ref<Class<Doc>> = core.class.Doc,
|
||||
singleInput = false
|
||||
): Promise<FindResult<Action>> {
|
||||
const targets = await client.findAll(view.class.ActionTarget, {
|
||||
'context.mode': 'context'
|
||||
mode: ViewContextType = 'context'
|
||||
): Promise<Action[]> {
|
||||
const actions: Action[] = await client.findAll(view.class.Action, {
|
||||
'context.mode': mode
|
||||
})
|
||||
const tmap = new Map(targets.map((it) => [it.action, it.context?.group]))
|
||||
|
||||
const categories: Record<string, number> = { top: 1, filter: 50, tools: 100 }
|
||||
|
||||
let filter = targets.map((it) => it.action)
|
||||
let filteredActions = actions
|
||||
|
||||
if (Array.isArray(doc)) {
|
||||
for (const d of doc) {
|
||||
const b = filterActions(client, d, targets, derived)
|
||||
filter = filter.filter((it) => b.includes(it))
|
||||
filteredActions = filterActions(client, d, filteredActions, derived)
|
||||
}
|
||||
} else {
|
||||
filter = filterActions(client, doc, targets, derived)
|
||||
filteredActions = filterActions(client, doc, filteredActions, derived)
|
||||
}
|
||||
const result = await client.findAll(view.class.Action, {
|
||||
_id: { $in: filter },
|
||||
singleInput: { $in: [undefined, singleInput] }
|
||||
})
|
||||
result.sort((a, b) => {
|
||||
const aTarget = categories[tmap.get(a._id) ?? 'top'] ?? 0
|
||||
const bTarget = categories[tmap.get(b._id) ?? 'top'] ?? 0
|
||||
const inputVal: ViewActionInput[] = ['none']
|
||||
if (!Array.isArray(doc) || doc.length === 1) {
|
||||
inputVal.push('focus')
|
||||
inputVal.push('any')
|
||||
}
|
||||
if (Array.isArray(doc) && doc.length > 0) {
|
||||
inputVal.push('selection')
|
||||
inputVal.push('any')
|
||||
}
|
||||
filteredActions = filteredActions.filter((it) => inputVal.includes(it.input))
|
||||
filteredActions.sort((a, b) => {
|
||||
const aTarget = categories[a.context.group ?? 'top'] ?? 0
|
||||
const bTarget = categories[b.context.group ?? 'top'] ?? 0
|
||||
return aTarget - bTarget
|
||||
})
|
||||
return result
|
||||
return filteredActions
|
||||
}
|
||||
|
||||
export async function getContextActions (
|
||||
client: Client,
|
||||
target: Ref<Class<Doc>>,
|
||||
context: ViewContext,
|
||||
multiple: boolean
|
||||
): Promise<FindResult<Action>> {
|
||||
const desc = client.getHierarchy().getAncestors(target)
|
||||
const targets = await client.findAll(view.class.ActionTarget, {
|
||||
target: { $in: desc },
|
||||
'context.mode': context.mode,
|
||||
'context.application': { $in: [context.application, undefined] }
|
||||
})
|
||||
return await client.findAll(view.class.Action, {
|
||||
_id: { $in: targets.map((it) => it.action) },
|
||||
singleInput: { $in: [undefined, !multiple] }
|
||||
})
|
||||
doc: Doc | Doc[],
|
||||
context: {
|
||||
mode: ViewContextType
|
||||
application?: Ref<Doc>
|
||||
}
|
||||
): Promise<Action[]> {
|
||||
const result = await getActions(client, doc, undefined, context.mode)
|
||||
|
||||
if (context.application !== undefined) {
|
||||
return result.filter((it) => it.context.application === context.application || it.context.application === undefined)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function filterActions (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function filterActions (
|
||||
client: Client,
|
||||
doc: Doc,
|
||||
targets: ActionTarget[],
|
||||
actions: Array<WithLookup<Action>>,
|
||||
derived: Ref<Class<Doc>> = core.class.Doc
|
||||
): Array<Ref<Action>> {
|
||||
const result: Array<Ref<Action>> = []
|
||||
): Array<WithLookup<Action>> {
|
||||
const result: Array<WithLookup<Action>> = []
|
||||
const hierarchy = client.getHierarchy()
|
||||
const clazz = hierarchy.getClass(doc._class)
|
||||
const ignoreActions = hierarchy.as(clazz, view.mixin.IgnoreActions)
|
||||
const ignore = ignoreActions?.actions ?? []
|
||||
for (const target of targets) {
|
||||
if (ignore.includes(target.action)) {
|
||||
for (const action of actions) {
|
||||
if (ignore.includes(action._id)) {
|
||||
continue
|
||||
}
|
||||
if (target.query !== undefined) {
|
||||
const r = matchQuery([doc], target.query, doc._class, hierarchy)
|
||||
if (action.query !== undefined) {
|
||||
const r = matchQuery([doc], action.query, doc._class, hierarchy)
|
||||
if (r.length === 0) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (hierarchy.isDerived(doc._class, target.target) && client.getHierarchy().isDerived(target.target, derived)) {
|
||||
result.push(target.action)
|
||||
if (
|
||||
(hierarchy.isDerived(doc._class, action.target) && client.getHierarchy().isDerived(action.target, derived)) ||
|
||||
(hierarchy.isMixin(action.target) && hierarchy.hasMixin(doc, action.target))
|
||||
) {
|
||||
result.push(action)
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
<!--
|
||||
// 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 { generateId } from '@anticrm/core'
|
||||
import { ViewContext } from '@anticrm/view'
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { Class, Doc, Ref, TxRemoveDoc } from '@anticrm/core'
|
||||
import core, { Doc, Ref, TxRemoveDoc } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { addTxListener, getClient } from '@anticrm/presentation'
|
||||
import { AnyComponent, Component } from '@anticrm/ui'
|
||||
import { Action, ViewContext } from '@anticrm/view'
|
||||
import { Action, ViewContextType } from '@anticrm/view'
|
||||
import { fly } from 'svelte/transition'
|
||||
import { getContextActions } from '../actions'
|
||||
import { getContextActions, getSelection } from '../actions'
|
||||
import { contextStore } from '../context'
|
||||
import { focusStore, previewDocument, selectionStore } from '../selection'
|
||||
import { getObjectPreview } from '../utils'
|
||||
@@ -15,8 +29,6 @@
|
||||
let actions: Action[] = []
|
||||
let q = 0
|
||||
|
||||
$: selectionClass = $focusStore.focus?._class
|
||||
|
||||
addTxListener((tx) => {
|
||||
if (tx._class === core.class.TxRemoveDoc) {
|
||||
const docId = (tx as TxRemoveDoc<Doc>).objectId
|
||||
@@ -28,56 +40,98 @@
|
||||
}
|
||||
})
|
||||
|
||||
// let lastKey: KeyboardEvent | undefined
|
||||
let lastKey: KeyboardEvent | undefined
|
||||
|
||||
async function updateActions (
|
||||
context: ViewContext,
|
||||
selectionClass: Ref<Class<Doc>> | undefined,
|
||||
multiSelection: boolean
|
||||
context: {
|
||||
mode: ViewContextType
|
||||
application?: Ref<Doc>
|
||||
},
|
||||
focus: Doc | undefined | null,
|
||||
selection: Doc[]
|
||||
): Promise<void> {
|
||||
const t = ++q
|
||||
const r = await getContextActions(client, selectionClass ?? core.class.Doc, context, multiSelection)
|
||||
|
||||
let docs: Doc | Doc[] = []
|
||||
if (selection.find((it) => it._id === focus?._id) === undefined && focus != null) {
|
||||
docs = focus
|
||||
} else {
|
||||
docs = selection
|
||||
}
|
||||
|
||||
const r = await getContextActions(client, docs, context)
|
||||
if (t === q) {
|
||||
actions = r
|
||||
}
|
||||
}
|
||||
$: ctx = $contextStore[$contextStore.length - 1]
|
||||
$: if (ctx !== undefined) updateActions(ctx, selectionClass, $selectionStore.length > 1)
|
||||
|
||||
function matchKey (key: KeyboardEvent, pattern: string): boolean {
|
||||
const fp = (
|
||||
$: ctx = $contextStore[$contextStore.length - 1]
|
||||
$: if (ctx !== undefined) {
|
||||
updateActions(
|
||||
{ mode: ctx.mode as ViewContextType, application: ctx.application },
|
||||
$focusStore.focus,
|
||||
$selectionStore
|
||||
)
|
||||
}
|
||||
function keyPrefix (key: KeyboardEvent): string {
|
||||
return (
|
||||
(key.altKey ? 'Alt + ' : '') +
|
||||
(key.shiftKey ? 'Shift + ' : '') +
|
||||
(key.metaKey ? 'Meta + ' : '') +
|
||||
(key.ctrlKey ? 'Ctrl + ' : '') +
|
||||
key.key
|
||||
).toLowerCase()
|
||||
const fp2 = (
|
||||
(key.altKey ? 'Alt + ' : '') +
|
||||
(key.shiftKey ? 'Shift + ' : '') +
|
||||
(key.metaKey ? 'Meta + ' : '') +
|
||||
(key.ctrlKey ? 'Ctrl + ' : '') +
|
||||
key.code
|
||||
).toLowerCase()
|
||||
return fp === pattern.toLowerCase() || fp2 === pattern.toLowerCase()
|
||||
(key.ctrlKey ? 'Ctrl + ' : '')
|
||||
)
|
||||
}
|
||||
function m (s1: string, s2: string): boolean {
|
||||
return s1 === s2.toLowerCase()
|
||||
}
|
||||
function matchKey (key: KeyboardEvent, pattern: string, lastKey?: KeyboardEvent): boolean {
|
||||
const fp = (keyPrefix(key) + key.key).toLowerCase()
|
||||
const fp2 = (keyPrefix(key) + key.code).toLowerCase()
|
||||
const lc = pattern.toLowerCase()
|
||||
if (m(fp, lc) || m(fp2, lc)) {
|
||||
return true
|
||||
}
|
||||
if (lastKey !== undefined) {
|
||||
// Check with last key
|
||||
const pfp = (keyPrefix(key) + lastKey.key).toLowerCase()
|
||||
const pfp2 = (keyPrefix(key) + lastKey.code).toLowerCase()
|
||||
|
||||
return m(`${pfp}->${fp}`, lc) || m(`${pfp2}->${fp}`, lc) || m(`${pfp}->${fp2}`, lc) || m(`${pfp2}->${fp2}`, lc)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
async function handleKeys (evt: KeyboardEvent): Promise<void> {
|
||||
const targetTagName = (evt.target as any)?.tagName?.toLowerCase()
|
||||
if (targetTagName === 'input' || targetTagName === 'button' || targetTagName === 'textarea') {
|
||||
console.log('no keyboard because', targetTagName, evt.target)
|
||||
let currentActions = actions
|
||||
|
||||
// For none we ignore all actions.
|
||||
if (ctx.mode === 'none') {
|
||||
return
|
||||
}
|
||||
// lastKey = evt
|
||||
for (const a of actions) {
|
||||
|
||||
const docs = getSelection($focusStore, $selectionStore)
|
||||
|
||||
if (targetTagName === 'input' || targetTagName === 'button' || targetTagName === 'textarea') {
|
||||
// Retrieve actual list of actions for input context
|
||||
currentActions = await getContextActions(client, docs, { ...ctx, mode: 'input' })
|
||||
}
|
||||
for (const a of currentActions) {
|
||||
// TODO: Handle multiple keys here
|
||||
if (a.keyBinding?.find((it) => matchKey(evt, it)) !== undefined) {
|
||||
if (a.keyBinding?.find((it) => matchKey(evt, it, lastKey)) !== undefined) {
|
||||
lastKey = undefined
|
||||
const action = await getResource(a.action)
|
||||
if (action !== undefined) {
|
||||
action($focusStore.focus, evt)
|
||||
await action($focusStore.focus, evt, a.actionProps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastKey = evt
|
||||
|
||||
setTimeout(() => {
|
||||
lastKey = undefined
|
||||
}, 500)
|
||||
}
|
||||
|
||||
let presenter: AnyComponent | undefined
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
<!--
|
||||
// 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 { WithLookup } from '@anticrm/core'
|
||||
import { getResource, translate } from '@anticrm/platform'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { closePopup, Icon, IconArrowLeft, Label } from '@anticrm/ui'
|
||||
import { Action, ViewContext } from '@anticrm/view'
|
||||
import { onMount } from 'svelte'
|
||||
import { filterActions, getSelection } from '../actions'
|
||||
import view from '../plugin'
|
||||
import { focusStore, selectionStore } from '../selection'
|
||||
import ActionContext from './ActionContext.svelte'
|
||||
import ListView from './ListView.svelte'
|
||||
import ObjectPresenter from './ObjectPresenter.svelte'
|
||||
|
||||
export let viewContext: ViewContext
|
||||
|
||||
let search: string = ''
|
||||
let actions: WithLookup<Action>[] = []
|
||||
let input: HTMLInputElement
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
query.query(
|
||||
view.class.Action,
|
||||
{},
|
||||
(res) => {
|
||||
actions = res
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
category: view.class.ActionCategory
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const targetQuery = createQuery()
|
||||
|
||||
targetQuery.query(view.class.Action, {}, (res) => {
|
||||
actions = res
|
||||
})
|
||||
|
||||
let supportedActions: WithLookup<Action>[] = []
|
||||
let filteredActions: WithLookup<Action>[] = []
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: {
|
||||
let fActions: WithLookup<Action>[] = actions
|
||||
|
||||
const docs = getSelection($focusStore, $selectionStore)
|
||||
for (const d of docs) {
|
||||
fActions = filterActions(client, d, fActions)
|
||||
}
|
||||
if (docs.length === 0) {
|
||||
fActions = fActions.filter((it) => it.input === 'none')
|
||||
}
|
||||
fActions = fActions.filter(
|
||||
(it) =>
|
||||
(it.$lookup?.category?.visible ?? true) &&
|
||||
(it.context.application === viewContext.application || it.context.application === undefined)
|
||||
)
|
||||
// Sort by category.
|
||||
supportedActions = fActions.sort((a, b) => a.category.localeCompare(b.category))
|
||||
}
|
||||
|
||||
async function filterSearchActions (actions: WithLookup<Action>[], search: string): Promise<void> {
|
||||
const res: WithLookup<Action>[] = []
|
||||
search = search.trim().toLowerCase()
|
||||
if (search.length > 0) {
|
||||
for (const a of actions) {
|
||||
const tr = await translate(a.label, {})
|
||||
if (tr.toLowerCase().indexOf(search) !== -1) {
|
||||
res.push(a)
|
||||
}
|
||||
}
|
||||
filteredActions = res
|
||||
} else {
|
||||
filteredActions = actions
|
||||
}
|
||||
}
|
||||
$: filterSearchActions(supportedActions, search)
|
||||
|
||||
let phTraslate: string = ''
|
||||
$: translate(view.string.ActionPlaceholder, {}).then((res) => {
|
||||
phTraslate = res
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
if (input) input.focus()
|
||||
})
|
||||
|
||||
let selection = 0
|
||||
let list: ListView
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
async function handleSelection (evt: Event, selection: number): Promise<void> {
|
||||
const action = filteredActions[selection]
|
||||
const docs = getSelection($focusStore, $selectionStore)
|
||||
if (action.input === 'focus') {
|
||||
const impl = await getResource(action.action)
|
||||
if (impl !== undefined) {
|
||||
closePopup()
|
||||
impl(docs[0], evt, action.actionProps)
|
||||
}
|
||||
}
|
||||
if (action.input === 'selection' || action.input === 'any' || action.input === 'none') {
|
||||
const impl = await getResource(action.action)
|
||||
if (impl !== undefined) {
|
||||
closePopup()
|
||||
impl(docs, evt, action.actionProps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onKeydown (key: KeyboardEvent): void {
|
||||
if (key.code === 'ArrowUp') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(selection - 1)
|
||||
}
|
||||
if (key.code === 'ArrowDown') {
|
||||
key.stopPropagation()
|
||||
key.preventDefault()
|
||||
list.select(selection + 1)
|
||||
}
|
||||
if (key.code === 'Enter') {
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
handleSelection(key, selection)
|
||||
}
|
||||
if (key.code === 'Escape') {
|
||||
key.preventDefault()
|
||||
key.stopPropagation()
|
||||
closePopup()
|
||||
}
|
||||
}
|
||||
function formatKey (key: string): string[][] {
|
||||
const thens = key.split('->')
|
||||
const result: string[][] = []
|
||||
for (const r of thens) {
|
||||
result.push(
|
||||
r.split('+').map((it) =>
|
||||
it
|
||||
.replaceAll('key', '')
|
||||
.replaceAll(/Meta|meta/g, '⌘')
|
||||
.replaceAll('ArrowUp', '↑')
|
||||
.replaceAll('ArrowDown', '↓')
|
||||
.replaceAll('ArrowLeft', '←')
|
||||
.replaceAll('ArrowRight', '→')
|
||||
.replaceAll('Backspace', '⌫')
|
||||
)
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionContext
|
||||
context={{
|
||||
mode: 'none'
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="selectPopup width-40" style:width="15rem" on:keydown={onKeydown}>
|
||||
<div class="mt-2 ml-2">
|
||||
{#if $selectionStore.length > 0}
|
||||
<div class="item-box">
|
||||
{$selectionStore.length} items
|
||||
</div>
|
||||
{:else if $focusStore.focus !== undefined}
|
||||
<div class="item-box">
|
||||
<ObjectPresenter
|
||||
objectId={$focusStore.focus._id}
|
||||
_class={$focusStore.focus._class}
|
||||
value={$focusStore.focus}
|
||||
props={{ inline: true }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="header">
|
||||
<input bind:this={input} type="text" bind:value={search} placeholder={phTraslate} />
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div class="box">
|
||||
<ListView
|
||||
bind:this={list}
|
||||
count={filteredActions.length}
|
||||
bind:selection
|
||||
on:click={(evt) => handleSelection(evt, evt.detail)}
|
||||
>
|
||||
<svelte:fragment slot="category" let:item>
|
||||
{@const action = filteredActions[item]}
|
||||
{#if item === 0 || (item > 0 && filteredActions[item - 1].$lookup?.category?.label !== action.$lookup?.category?.label)}
|
||||
<!--Category for first item-->
|
||||
{#if action.$lookup?.category}
|
||||
<div class="category-box">
|
||||
<Label label={action.$lookup.category.label} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="item" let:item>
|
||||
{@const action = filteredActions[item]}
|
||||
<div class="flex-row-center flex-between flex-grow ml-2 p-3 text-base">
|
||||
<div class="mr-4">
|
||||
<Icon icon={action.icon ?? IconArrowLeft} size={'small'} />
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<Label label={action.label} />
|
||||
</div>
|
||||
<div class="mr-2 text-md flex-row-center">
|
||||
{#if action.keyBinding}
|
||||
{#each action.keyBinding as key, i}
|
||||
{#if i !== 0}
|
||||
<div class="ml-2 mr-2">or</div>
|
||||
{/if}
|
||||
<div class="flex-row-center" class:ml-2={i !== 0}>
|
||||
{#each formatKey(key) as k, jj}
|
||||
{#if jj !== 0}
|
||||
<div class="ml-1 mr-1">then</div>
|
||||
{/if}
|
||||
{#each k as kk, j}
|
||||
<div class="flex-center text-sm key-box" class:ml-2={j !== 0}>
|
||||
{kk}
|
||||
</div>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</ListView>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.key-box {
|
||||
background-color: var(--divider-color);
|
||||
color: var(--caption-color);
|
||||
min-width: 1.5rem;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
.item-box {
|
||||
display: inline-block;
|
||||
background-color: var(--divider-color);
|
||||
color: var(--caption-color);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.category-box {
|
||||
display: inline-block;
|
||||
background-color: var(--divider-color);
|
||||
color: var(--caption-color);
|
||||
padding: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -75,6 +75,7 @@
|
||||
let collectionEditors: { key: KeyedAttribute; editor: AnyComponent }[] = []
|
||||
|
||||
let mixins: Mixin<Doc>[] = []
|
||||
|
||||
$: if (object) {
|
||||
parentClass = getParentClass(object._class)
|
||||
getMixins()
|
||||
@@ -147,6 +148,7 @@
|
||||
|
||||
let mainEditor: AnyComponent
|
||||
$: if (object) getEditorOrDefault(object._class, object._class)
|
||||
|
||||
async function getEditorOrDefault (_class: Ref<Class<Doc>> | undefined, defaultClass: Ref<Class<Doc>>): Promise<void> {
|
||||
let editor = _class !== undefined ? await getEditor(_class) : undefined
|
||||
if (editor === undefined) {
|
||||
|
||||
@@ -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 { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let selection: number = 0
|
||||
export let count: number
|
||||
|
||||
const refs: HTMLElement[] = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let oldSelection = Date.now()
|
||||
|
||||
function onRow (item: number): void {
|
||||
if (updateSelection(item)) {
|
||||
dispatch('on-select', item)
|
||||
}
|
||||
}
|
||||
|
||||
function updateSelection (item: number): boolean {
|
||||
const now = Date.now()
|
||||
if (now - oldSelection >= 25) {
|
||||
selection = item
|
||||
oldSelection = now
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function select (pos: number): void {
|
||||
if (pos < 0) {
|
||||
pos = 0
|
||||
}
|
||||
if (pos >= count) {
|
||||
pos = count - 1
|
||||
}
|
||||
const r = refs[pos]
|
||||
onRow(pos)
|
||||
if (r !== undefined) {
|
||||
r?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if count}
|
||||
<div class="list-container flex-col flex-grow" style:overflow={'auto'}>
|
||||
{#each Array(count) as _, row}
|
||||
<slot name="category" item={row} />
|
||||
<div
|
||||
class:selection={row === selection}
|
||||
on:mouseover={() => onRow(row)}
|
||||
on:focus={() => {}}
|
||||
bind:this={refs[row]}
|
||||
on:click={() => dispatch('click', row)}
|
||||
>
|
||||
<slot name="item" item={row} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.list-container {
|
||||
background-color: var(--board-card-bg-color);
|
||||
border-radius: 0.25rem;
|
||||
user-select: none;
|
||||
|
||||
.selection {
|
||||
background-color: var(--theme-bg-checked-hover);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -27,18 +27,18 @@
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function invokeAction (evt: Event, action: ViewAction) {
|
||||
async function invokeAction (evt: Event, action: ViewAction, props?: Record<string, any>) {
|
||||
const impl = await getResource(action)
|
||||
await impl(Array.isArray(object) && object.length === 1 ? object[0] : object, evt)
|
||||
await impl(Array.isArray(object) && object.length === 1 ? object[0] : object, evt, props)
|
||||
}
|
||||
let loaded = 0
|
||||
|
||||
getActions(client, object, baseMenuClass, !Array.isArray(object) || object.length === 1).then((result) => {
|
||||
getActions(client, object, baseMenuClass).then((result) => {
|
||||
actions = result.map((a) => ({
|
||||
label: a.label,
|
||||
icon: a.icon as Asset,
|
||||
action: async (_: any, evt: Event) => {
|
||||
invokeAction(evt, a.action)
|
||||
invokeAction(evt, a.action, a.actionProps)
|
||||
}
|
||||
}))
|
||||
loaded = 1
|
||||
|
||||
@@ -37,6 +37,7 @@ import TimestampPresenter from './components/TimestampPresenter.svelte'
|
||||
import UpDownNavigator from './components/UpDownNavigator.svelte'
|
||||
import GithubPresenter from './components/linkPresenters/GithubPresenter.svelte'
|
||||
import YoutubePresenter from './components/linkPresenters/YoutubePresenter.svelte'
|
||||
import ActionsPopup from './components/ActionsPopup.svelte'
|
||||
|
||||
export { getActions } from './actions'
|
||||
export { default as ActionContext } from './components/ActionContext.svelte'
|
||||
@@ -69,6 +70,7 @@ export default async (): Promise<Resources> => ({
|
||||
HTMLPresenter,
|
||||
IntlStringPresenter,
|
||||
GithubPresenter,
|
||||
YoutubePresenter
|
||||
YoutubePresenter,
|
||||
ActionsPopup
|
||||
}
|
||||
})
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
//
|
||||
|
||||
import { IntlString, mergeIds } from '@anticrm/platform'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import view, { viewId } from '@anticrm/view'
|
||||
|
||||
export default mergeIds(viewId, view, {
|
||||
component: {
|
||||
ActionsPopup: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
MoveClass: '' as IntlString,
|
||||
SelectToMove: '' as IntlString,
|
||||
@@ -30,6 +34,7 @@ export default mergeIds(viewId, view, {
|
||||
DeleteObject: '' as IntlString,
|
||||
DeleteObjectConfirm: '' as IntlString,
|
||||
Assignees: '' as IntlString,
|
||||
Labels: '' as IntlString
|
||||
Labels: '' as IntlString,
|
||||
ActionPlaceholder: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
+83
-21
@@ -30,6 +30,7 @@ import type {
|
||||
import type { Asset, IntlString, Plugin, Resource, Status } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
|
||||
import { PopupPosAlignment } from '@anticrm/ui/src/types'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -116,41 +117,70 @@ export type KeyBinding = string
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type ViewAction = Resource<(doc: Doc | Doc[] | undefined, evt: Event) => Promise<void>>
|
||||
export type ViewActionInput = 'focus' | 'selection' | 'any' | 'none'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Action extends Doc, UXObject {
|
||||
keyBinding?: KeyBinding[]
|
||||
action: ViewAction
|
||||
export type ViewAction<T = Record<string, any>> = Resource<
|
||||
(doc: Doc | Doc[] | undefined, evt: Event, params?: T) => Promise<void>
|
||||
>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ActionCategory extends Doc, UXObject {
|
||||
// Does category is visible for use in popup.
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Action<T extends Doc = Doc, P = Record<string, any>> extends Doc, UXObject {
|
||||
// Action implementation details
|
||||
action: ViewAction<P>
|
||||
// Action implementation parameters
|
||||
actionProps?: P
|
||||
|
||||
// If specified, action could be used only with one item selected.
|
||||
// By default it is treated as false
|
||||
singleInput?: boolean
|
||||
}
|
||||
// single - one object is required
|
||||
// any - one or multiple objects are required
|
||||
// any - any input is suitable.
|
||||
input: ViewActionInput
|
||||
|
||||
/**
|
||||
* Define action to 'object' mapping.
|
||||
* @public
|
||||
*/
|
||||
export interface ActionTarget<T extends Doc = Doc> extends Doc {
|
||||
target: Ref<Class<T>>
|
||||
action: Ref<Action>
|
||||
// Focus and/or all selection document should match target class.
|
||||
target: Ref<Class<Doc>>
|
||||
// Action is applicable only for objects matching criteria
|
||||
query?: DocumentQuery<T>
|
||||
context: ViewContext
|
||||
|
||||
// If specified, will be used instead of action from Action.
|
||||
override?: ViewAction
|
||||
// If defined, types should be matched to proposed list
|
||||
inputProps?: Record<string, Ref<Class<Doc>>>
|
||||
|
||||
// Kayboard bindings
|
||||
keyBinding?: KeyBinding[]
|
||||
|
||||
// short description for action.
|
||||
description?: IntlString
|
||||
|
||||
// Action category, for UI.
|
||||
category: Ref<ActionCategory>
|
||||
|
||||
// Context action is defined for
|
||||
context: ViewContext
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* context - only for context menu actions.
|
||||
* workbench - global actions per application or entire workbench.
|
||||
* browser - actions for list/table/kanban browsing.
|
||||
* editor - actions for selected editor context.
|
||||
* context - only for context menu actions.
|
||||
* panel - for panel based actions.
|
||||
* popup - for popup based actions, like Close of Popup.
|
||||
* input - for input based actions, some actions should be available for input controls.
|
||||
*/
|
||||
export type ViewContextType = 'context' | 'workbench' | 'browser' | 'editor' | 'panel' | 'popup' | 'context'
|
||||
export type ViewContextType = 'context' | 'workbench' | 'browser' | 'editor' | 'panel' | 'popup' | 'input' | 'none'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -272,7 +302,7 @@ const view = plugin(viewId, {
|
||||
ViewletDescriptor: '' as Ref<Class<ViewletDescriptor>>,
|
||||
Viewlet: '' as Ref<Class<Viewlet>>,
|
||||
Action: '' as Ref<Class<Action>>,
|
||||
ActionTarget: '' as Ref<Class<ActionTarget>>,
|
||||
ActionCategory: '' as Ref<Class<ActionCategory>>,
|
||||
LinkPresenter: '' as Ref<Class<LinkPresenter>>
|
||||
},
|
||||
viewlet: {
|
||||
@@ -291,7 +321,39 @@ const view = plugin(viewId, {
|
||||
Move: '' as Asset,
|
||||
Archive: '' as Asset,
|
||||
Statuses: '' as Asset,
|
||||
Open: '' as Asset
|
||||
Open: '' as Asset,
|
||||
ArrowRight: '' as Asset
|
||||
},
|
||||
category: {
|
||||
General: '' as Ref<ActionCategory>,
|
||||
GeneralNavigation: '' as Ref<ActionCategory>,
|
||||
Navigation: '' as Ref<ActionCategory>,
|
||||
Editor: '' as Ref<ActionCategory>,
|
||||
MarkdownFormatting: '' as Ref<ActionCategory>
|
||||
},
|
||||
actionImpl: {
|
||||
UpdateDocument: '' as ViewAction<{
|
||||
key: string
|
||||
value: any
|
||||
ask?: boolean
|
||||
label?: IntlString
|
||||
message?: IntlString
|
||||
}>,
|
||||
ShowPanel: '' as ViewAction<{
|
||||
component?: AnyComponent
|
||||
element?: PopupPosAlignment
|
||||
rightSection?: AnyComponent
|
||||
}>,
|
||||
ShowPopup: '' as ViewAction<{
|
||||
component: AnyComponent
|
||||
element?: PopupPosAlignment
|
||||
_id?: string
|
||||
_class?: string
|
||||
_space?: string
|
||||
value?: string
|
||||
values?: string
|
||||
props?: Record<string, any>
|
||||
}>
|
||||
}
|
||||
})
|
||||
export default view
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
import { Avatar, createQuery, setClient } from '@anticrm/presentation'
|
||||
import {
|
||||
AnyComponent,
|
||||
closePanel,
|
||||
closePopup,
|
||||
closeTooltip,
|
||||
Component,
|
||||
DatePickerPopup,
|
||||
getCurrentLocation,
|
||||
location,
|
||||
Location,
|
||||
@@ -33,12 +33,12 @@
|
||||
PanelInstance,
|
||||
Popup,
|
||||
showPopup,
|
||||
DatePickerPopup,
|
||||
TooltipInstance
|
||||
} from '@anticrm/ui'
|
||||
import { ActionContext, ActionHandler } from '@anticrm/view-resources'
|
||||
import type { Application, NavigatorModel, SpecialNavModel, ViewConfiguration } from '@anticrm/workbench'
|
||||
import { onDestroy, tick } from 'svelte'
|
||||
import { doNavigate } from '../utils'
|
||||
import workbench from '../plugin'
|
||||
import AccountPopup from './AccountPopup.svelte'
|
||||
import AppItem from './AppItem.svelte'
|
||||
@@ -182,36 +182,31 @@
|
||||
}
|
||||
visibileNav = true
|
||||
|
||||
closePanel()
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[1] = app._id
|
||||
loc.path.length = 2
|
||||
navigate(loc)
|
||||
doNavigate([], undefined, {
|
||||
mode: 'app',
|
||||
application: app._id
|
||||
})
|
||||
}
|
||||
|
||||
function selectSpecial (id: string): void {
|
||||
if (currentSpecial === id) return
|
||||
closePanel()
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = id
|
||||
loc.path.length = 3
|
||||
|
||||
doNavigate([], undefined, {
|
||||
mode: 'special',
|
||||
special: id
|
||||
})
|
||||
checkOnHide()
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
function selectSpace (spaceId?: Ref<Space>, spaceSpecial?: string): void {
|
||||
if (currentSpace === spaceId && (spaceSpecial === currentSpecial || spaceSpecial === asideId)) return
|
||||
closePanel()
|
||||
const loc = getCurrentLocation()
|
||||
if (spaceId !== undefined) {
|
||||
loc.path[2] = spaceId
|
||||
const special = spaceSpecial
|
||||
if (special !== undefined) {
|
||||
loc.path[3] = special
|
||||
}
|
||||
}
|
||||
|
||||
doNavigate([], undefined, {
|
||||
mode: 'space',
|
||||
space: spaceId,
|
||||
spaceSpecial: spaceSpecial
|
||||
})
|
||||
checkOnHide()
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
function closeAside (): void {
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
const addSpace: Action = {
|
||||
label: model.addSpaceLabel,
|
||||
icon: IconAdd,
|
||||
action: async (_id: Ref<Doc>, ev?: Event): Promise<void> => {
|
||||
action: async (_id: Ref<Doc>): Promise<void> => {
|
||||
dispatch('open')
|
||||
showPopup(model.createComponent, {}, 'top')
|
||||
}
|
||||
@@ -61,7 +61,7 @@
|
||||
const browseSpaces: Action = {
|
||||
label: plugin.string.BrowseSpaces,
|
||||
icon: IconSearch,
|
||||
action: async (_id: Ref<Doc>, ev?: Event): Promise<void> => {
|
||||
action: async (_id: Ref<Doc>): Promise<void> => {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = 'spaceBrowser'
|
||||
dispatch('open')
|
||||
@@ -111,7 +111,7 @@
|
||||
label: act.label,
|
||||
action: async (evt: Event) => {
|
||||
const impl = await getResource(act.action)
|
||||
await impl(space, evt)
|
||||
await impl(space, evt, act.actionProps)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
label: act.label,
|
||||
action: async (evt: Event) => {
|
||||
const impl = await getResource(act.action)
|
||||
await impl(space, evt)
|
||||
await impl(space, evt, act.actionProps)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import WorkbenchApp from './components/WorkbenchApp.svelte'
|
||||
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import Archive from './components/Archive.svelte'
|
||||
import { Space } from '@anticrm/core'
|
||||
import { Resources } from '@anticrm/platform'
|
||||
import ApplicationPresenter from './components/ApplicationPresenter.svelte'
|
||||
import Archive from './components/Archive.svelte'
|
||||
import SpacePanel from './components/navigator/SpacePanel.svelte'
|
||||
import SpaceBrowser from './components/SpaceBrowser.svelte'
|
||||
import WorkbenchApp from './components/WorkbenchApp.svelte'
|
||||
import { doNavigate } from './utils'
|
||||
|
||||
function hasArchiveSpaces (spaces: Space[]): boolean {
|
||||
return spaces.find((sp) => sp.archived) !== undefined
|
||||
@@ -35,5 +36,8 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
function: {
|
||||
HasArchiveSpaces: hasArchiveSpaces
|
||||
},
|
||||
actionImpl: {
|
||||
Navigate: doNavigate
|
||||
}
|
||||
})
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Class, Client, Obj, Ref, Space } from '@anticrm/core'
|
||||
import type { Class, Client, Doc, Obj, Ref, Space } from '@anticrm/core'
|
||||
import type { Asset } from '@anticrm/platform'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { NavigatorModel } from '@anticrm/workbench'
|
||||
import { Application, NavigatorModel } from '@anticrm/workbench'
|
||||
import view from '@anticrm/view'
|
||||
import { closePanel, getCurrentLocation, navigate } from '@anticrm/ui'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
|
||||
export function classIcon (client: Client, _class: Ref<Class<Obj>>): Asset | undefined {
|
||||
return client.getHierarchy().getClass(_class).icon
|
||||
@@ -43,3 +45,68 @@ export async function getSpaceName (client: Client, space: Space): Promise<strin
|
||||
|
||||
return space.name
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function doNavigate (
|
||||
doc: Doc | Doc[],
|
||||
evt: Event | undefined,
|
||||
props: {
|
||||
mode: 'app' | 'special' | 'space'
|
||||
application?: Ref<Application>
|
||||
special?: string
|
||||
spaceSpecial?: string
|
||||
space?: Ref<Space>
|
||||
// If no space is selected, select first space from list
|
||||
spaceClass?: Ref<Class<Space>>
|
||||
}
|
||||
): Promise<void> {
|
||||
evt?.preventDefault()
|
||||
|
||||
closePanel()
|
||||
const loc = getCurrentLocation()
|
||||
const client = getClient()
|
||||
switch (props.mode) {
|
||||
case 'app':
|
||||
loc.path[1] = props.application ?? ''
|
||||
if (props.special !== undefined) {
|
||||
loc.path[2] = props.special
|
||||
loc.path.length = 3
|
||||
} else {
|
||||
loc.path.length = 2
|
||||
}
|
||||
navigate(loc)
|
||||
break
|
||||
case 'special':
|
||||
if (props.application !== undefined && loc.path[1] !== props.application) {
|
||||
loc.path[1] = props.application
|
||||
}
|
||||
loc.path[2] = props.special ?? ''
|
||||
loc.path.length = 3
|
||||
navigate(loc)
|
||||
break
|
||||
case 'space': {
|
||||
if (props.space !== undefined) {
|
||||
loc.path[2] = props.space
|
||||
}
|
||||
if (props.spaceSpecial !== undefined) {
|
||||
loc.path[3] = props.spaceSpecial
|
||||
}
|
||||
if (props.spaceClass !== undefined) {
|
||||
const ex = await client.findOne(props.spaceClass, { _id: loc.path[2] as Ref<Space> })
|
||||
if (ex === undefined) {
|
||||
const r = await client.findOne(props.spaceClass, {})
|
||||
if (r !== undefined) {
|
||||
loc.path[3] = r._id
|
||||
navigate(loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
loc.path.length = 4
|
||||
navigate(loc)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"dependencies": {
|
||||
"@anticrm/core": "~0.6.16",
|
||||
"@anticrm/platform": "~0.6.6",
|
||||
"@anticrm/ui": "~0.6.0"
|
||||
"@anticrm/ui": "~0.6.0",
|
||||
"@anticrm/view": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { Class, Doc, Mixin, Obj, Ref, Space } from '@anticrm/core'
|
||||
import type { Asset, IntlString, Metadata, Plugin, Resource } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
import { ViewAction } from '@anticrm/view'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -113,5 +114,16 @@ export default plugin(workbenchId, {
|
||||
metadata: {
|
||||
PlatformTitle: '' as Metadata<string>,
|
||||
ExcludedApplications: '' as Metadata<Ref<Application>[]>
|
||||
},
|
||||
actionImpl: {
|
||||
Navigate: '' as ViewAction<{
|
||||
mode: 'app' | 'special' | 'space'
|
||||
application?: Ref<Application>
|
||||
special?: string
|
||||
space?: Ref<Space>
|
||||
// If no space is selected, select first space from list
|
||||
spaceClass?: Ref<Class<Space>>
|
||||
spaceSpecial?: string
|
||||
}>
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user