mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-22 16:27:22 +00:00
parent
ec738daf75
commit
56380c4958
@ -14,13 +14,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
// To help typescript locate view plugin properly
|
// To help typescript locate view plugin properly
|
||||||
import type {} from '@anticrm/view'
|
import type { ActionTarget } from '@anticrm/view'
|
||||||
|
|
||||||
import attachment from '@anticrm/model-attachment'
|
import attachment from '@anticrm/model-attachment'
|
||||||
import type { Employee } from '@anticrm/contact'
|
import type { Employee } from '@anticrm/contact'
|
||||||
import contact from '@anticrm/contact'
|
import contact from '@anticrm/contact'
|
||||||
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space } from '@anticrm/core'
|
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space, Timestamp } from '@anticrm/core'
|
||||||
import { Builder, Collection, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
|
import { Builder, Collection, Mixin, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||||
import chunter from '@anticrm/model-chunter'
|
import chunter from '@anticrm/model-chunter'
|
||||||
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
|
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
|
||||||
import view from '@anticrm/model-view'
|
import view from '@anticrm/model-view'
|
||||||
@ -42,7 +42,8 @@ import type {
|
|||||||
WonStateTemplate,
|
WonStateTemplate,
|
||||||
LostStateTemplate,
|
LostStateTemplate,
|
||||||
KanbanTemplate,
|
KanbanTemplate,
|
||||||
Task
|
Task,
|
||||||
|
TodoItem
|
||||||
} from '@anticrm/task'
|
} from '@anticrm/task'
|
||||||
import { createProjectKanban } from '@anticrm/task'
|
import { createProjectKanban } from '@anticrm/task'
|
||||||
import task from './plugin'
|
import task from './plugin'
|
||||||
@ -99,6 +100,22 @@ export class TTask extends TAttachedDoc implements Task {
|
|||||||
assignee!: Ref<Employee> | null
|
assignee!: Ref<Employee> | null
|
||||||
|
|
||||||
declare rank: string
|
declare rank: string
|
||||||
|
|
||||||
|
@Prop(Collection(task.class.TodoItem), "Todo's" as IntlString)
|
||||||
|
todoItems!: number
|
||||||
|
}
|
||||||
|
|
||||||
|
@Model(task.class.TodoItem, core.class.AttachedDoc, DOMAIN_TASK)
|
||||||
|
@UX('Todo' as IntlString)
|
||||||
|
export class TTodoItem extends TAttachedDoc implements TodoItem {
|
||||||
|
@Prop(TypeString(), 'Name' as IntlString, task.icon.Task)
|
||||||
|
name!: string
|
||||||
|
|
||||||
|
@Prop(TypeBoolean(), 'Complete' as IntlString)
|
||||||
|
done!: boolean
|
||||||
|
|
||||||
|
@Prop(TypeDate(), 'Due date' as IntlString)
|
||||||
|
dueTo?: Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(task.class.SpaceWithStates, core.class.Space)
|
@Model(task.class.SpaceWithStates, core.class.Space)
|
||||||
@ -212,7 +229,8 @@ export function createModel (builder: Builder): void {
|
|||||||
TTask,
|
TTask,
|
||||||
TSpaceWithStates,
|
TSpaceWithStates,
|
||||||
TProject,
|
TProject,
|
||||||
TIssue
|
TIssue,
|
||||||
|
TTodoItem
|
||||||
)
|
)
|
||||||
builder.mixin(task.class.Project, core.class.Class, workbench.mixin.SpaceView, {
|
builder.mixin(task.class.Project, core.class.Class, workbench.mixin.SpaceView, {
|
||||||
view: {
|
view: {
|
||||||
@ -389,6 +407,51 @@ export function createModel (builder: Builder): void {
|
|||||||
builder.mixin(task.class.DoneState, core.class.Class, view.mixin.AttributePresenter, {
|
builder.mixin(task.class.DoneState, core.class.Class, view.mixin.AttributePresenter, {
|
||||||
presenter: task.component.DoneStatePresenter
|
presenter: task.component.DoneStatePresenter
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.AttributeEditor, {
|
||||||
|
editor: task.component.Todos
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.AttributePresenter, {
|
||||||
|
presenter: task.component.TodoItemPresenter
|
||||||
|
})
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
view.class.Action,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
label: 'Mark as done' as IntlString,
|
||||||
|
icon: task.icon.TodoCheck,
|
||||||
|
action: task.actionImpl.TodoItemMarkDone
|
||||||
|
},
|
||||||
|
task.action.TodoItemMarkDone
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
view.class.Action,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
label: 'Mark as undone' as IntlString,
|
||||||
|
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,
|
||||||
|
query: {
|
||||||
|
done: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
builder.createDoc(view.class.ActionTarget, core.space.Model, {
|
||||||
|
target: task.class.TodoItem,
|
||||||
|
action: task.action.TodoItemMarkUnDone,
|
||||||
|
query: {
|
||||||
|
done: true
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export { taskOperation } from './migration'
|
export { taskOperation } from './migration'
|
||||||
|
@ -28,11 +28,15 @@ export default mergeIds(taskId, task, {
|
|||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
CreateTask: '' as Ref<Action>,
|
CreateTask: '' as Ref<Action>,
|
||||||
EditStatuses: '' as Ref<Action>
|
EditStatuses: '' as Ref<Action>,
|
||||||
|
TodoItemMarkDone: '' as Ref<Action>,
|
||||||
|
TodoItemMarkUnDone: '' as Ref<Action>
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
CreateTask: '' as Resource<(object: Doc) => Promise<void>>,
|
CreateTask: '' as Resource<(object: Doc) => Promise<void>>,
|
||||||
EditStatuses: '' as Resource<(object: Doc) => Promise<void>>
|
EditStatuses: '' as Resource<(object: Doc) => Promise<void>>,
|
||||||
|
TodoItemMarkDone: '' as Resource<(object: Doc) => Promise<void>>,
|
||||||
|
TodoItemMarkUnDone: '' as Resource<(object: Doc) => Promise<void>>
|
||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
ProjectView: '' as AnyComponent,
|
ProjectView: '' as AnyComponent,
|
||||||
@ -46,7 +50,9 @@ export default mergeIds(taskId, task, {
|
|||||||
StatePresenter: '' as AnyComponent,
|
StatePresenter: '' as AnyComponent,
|
||||||
DoneStatePresenter: '' as AnyComponent,
|
DoneStatePresenter: '' as AnyComponent,
|
||||||
StateEditor: '' as AnyComponent,
|
StateEditor: '' as AnyComponent,
|
||||||
KanbanView: '' as AnyComponent
|
KanbanView: '' as AnyComponent,
|
||||||
|
Todos: '' as AnyComponent,
|
||||||
|
TodoItemPresenter: '' as AnyComponent
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
Task: '' as IntlString,
|
Task: '' as IntlString,
|
||||||
|
@ -18,7 +18,7 @@ import clone from 'just-clone'
|
|||||||
import type { Class, Doc, Ref } from './classes'
|
import type { Class, Doc, Ref } from './classes'
|
||||||
import core from './component'
|
import core from './component'
|
||||||
import { Hierarchy } from './hierarchy'
|
import { Hierarchy } from './hierarchy'
|
||||||
import { findProperty, resultSort } from './query'
|
import { matchQuery, resultSort } from './query'
|
||||||
import type { DocumentQuery, FindOptions, FindResult, LookupData, Refs, Storage, TxResult, WithLookup } from './storage'
|
import type { DocumentQuery, FindOptions, FindResult, LookupData, Refs, Storage, TxResult, WithLookup } from './storage'
|
||||||
import type { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
|
import type { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||||
import { TxProcessor } from './tx'
|
import { TxProcessor } from './tx'
|
||||||
@ -108,11 +108,7 @@ export abstract class MemDb extends TxProcessor {
|
|||||||
result = this.getObjectsByClass(_class)
|
result = this.getObjectsByClass(_class)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in query) {
|
result = matchQuery(result, query)
|
||||||
if (key === '_id' && ((query._id as any)?.$like === undefined || query._id === undefined)) continue
|
|
||||||
const value = (query as any)[key]
|
|
||||||
result = findProperty(result, key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.lookup !== undefined) result = this.lookup(result as T[], options.lookup)
|
if (options?.lookup !== undefined) result = this.lookup(result as T[], options.lookup)
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { DocumentQuery } from '.'
|
||||||
import { Doc } from './classes'
|
import { Doc } from './classes'
|
||||||
import { createPredicates, isPredicate } from './predicate'
|
import { createPredicates, isPredicate } from './predicate'
|
||||||
import { SortingQuery } from './storage'
|
import { SortingQuery } from './storage'
|
||||||
@ -102,3 +103,18 @@ function getValue (key: string, obj: any): any {
|
|||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function matchQuery<T extends Doc> (docs: Doc[], query: DocumentQuery<T>): Doc[] {
|
||||||
|
let result = [...docs]
|
||||||
|
for (const key in query) {
|
||||||
|
if (key === '_id' && ((query._id as any)?.$like === undefined || query._id === undefined)) continue
|
||||||
|
const value = (query as any)[key]
|
||||||
|
result = findProperty(result, key, value)
|
||||||
|
if (result.length === 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -26,14 +26,17 @@
|
|||||||
import SpaceSelect from './SpaceSelect.svelte'
|
import SpaceSelect from './SpaceSelect.svelte'
|
||||||
import presentation from '..'
|
import presentation from '..'
|
||||||
|
|
||||||
export let spaceClass: Ref<Class<Space>>
|
export let spaceClass: Ref<Class<Space>> | undefined = undefined
|
||||||
export let space: Ref<Space>
|
export let space: Ref<Space>
|
||||||
export let spaceLabel: IntlString
|
export let spaceLabel: IntlString | undefined = undefined
|
||||||
export let spacePlaceholder: IntlString
|
export let spacePlaceholder: IntlString | undefined = undefined
|
||||||
export let label: IntlString
|
export let label: IntlString
|
||||||
export let okAction: () => void
|
export let okAction: () => void
|
||||||
export let canSave: boolean = false
|
export let canSave: boolean = false
|
||||||
|
|
||||||
|
export let okLabel: IntlString = presentation.string.Create
|
||||||
|
export let cancelLabel: IntlString = presentation.string.Cancel
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -48,13 +51,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="content"><slot /></div>
|
<div class="content"><slot /></div>
|
||||||
|
{#if spaceClass && spaceLabel && spacePlaceholder}
|
||||||
<div class="flex-col pool">
|
<div class="flex-col pool">
|
||||||
<div class="separator" />
|
<div class="separator" />
|
||||||
<SpaceSelect _class={spaceClass} label={spaceLabel} placeholder={spacePlaceholder} bind:value={space} />
|
<SpaceSelect _class={spaceClass} label={spaceLabel} placeholder={spacePlaceholder} bind:value={space} />
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<Button disabled={!canSave} label={presentation.string.Create} size={'small'} transparent primary on:click={() => { okAction(); dispatch('close') }} />
|
<Button disabled={!canSave} label={okLabel} size={'small'} transparent primary on:click={() => { okAction(); dispatch('close') }} />
|
||||||
<Button label={presentation.string.Cancel} size={'small'} transparent on:click={() => { dispatch('close') }} />
|
<Button label={cancelLabel} size={'small'} transparent on:click={() => { dispatch('close') }} />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
showPopup,
|
showPopup,
|
||||||
TimeSince
|
TimeSince
|
||||||
} from '@anticrm/ui'
|
} from '@anticrm/ui'
|
||||||
import type { Action, AttributeModel } from '@anticrm/view'
|
import type { AttributeModel } from '@anticrm/view'
|
||||||
import { buildModel, getActions, getObjectPresenter } from '@anticrm/view-resources'
|
import { buildModel, getActions, getObjectPresenter } from '@anticrm/view-resources'
|
||||||
import { activityKey, ActivityKey, DisplayTx } from '../activity'
|
import { activityKey, ActivityKey, DisplayTx } from '../activity'
|
||||||
import ShowMore from './ShowMore.svelte'
|
import ShowMore from './ShowMore.svelte'
|
||||||
@ -53,7 +53,6 @@
|
|||||||
let props: any
|
let props: any
|
||||||
let employee: EmployeeAccount | undefined
|
let employee: EmployeeAccount | undefined
|
||||||
let model: AttributeModel[] = []
|
let model: AttributeModel[] = []
|
||||||
let actions: Action[] = []
|
|
||||||
|
|
||||||
let edit = false
|
let edit = false
|
||||||
|
|
||||||
@ -74,7 +73,7 @@
|
|||||||
}
|
}
|
||||||
const docClass: Class<Doc> = client.getModel().getObject(doc._class)
|
const docClass: Class<Doc> = client.getModel().getObject(doc._class)
|
||||||
|
|
||||||
const presenter = await getObjectPresenter(client, doc._class, 'doc-presenter')
|
const presenter = await getObjectPresenter(client, doc._class, { key: 'doc-presenter' })
|
||||||
if (presenter !== undefined) {
|
if (presenter !== undefined) {
|
||||||
return {
|
return {
|
||||||
display: 'inline',
|
display: 'inline',
|
||||||
@ -125,10 +124,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$: getActions(client, tx.tx.objectClass).then((result) => {
|
|
||||||
actions = result
|
|
||||||
})
|
|
||||||
|
|
||||||
async function getValue (m: AttributeModel, utx: TxUpdateDoc<Doc>): Promise<any> {
|
async function getValue (m: AttributeModel, utx: TxUpdateDoc<Doc>): Promise<any> {
|
||||||
const val = (utx.operations as any)[m.key]
|
const val = (utx.operations as any)[m.key]
|
||||||
console.log(m._class, m.key, val, typeof val)
|
console.log(m._class, m.key, val, typeof val)
|
||||||
@ -140,6 +135,7 @@
|
|||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
const showMenu = async (ev: MouseEvent): Promise<void> => {
|
const showMenu = async (ev: MouseEvent): Promise<void> => {
|
||||||
|
const actions = await getActions(client, tx.doc as Doc)
|
||||||
showPopup(
|
showPopup(
|
||||||
Menu,
|
Menu,
|
||||||
{
|
{
|
||||||
|
@ -12,4 +12,11 @@
|
|||||||
<path d="M14.4,8c0,3.5-2.9,6.4-6.4,6.4c-3.5,0-6.4-2.9-6.4-6.4c0-3.5,2.9-6.4,6.4-6.4c0.1-0.4,0.3-0.8,0.5-1.2c-0.2,0-0.3,0-0.5,0 C3.8,0.4,0.4,3.8,0.4,8c0,4.2,3.4,7.6,7.6,7.6s7.6-3.4,7.6-7.6c0-0.2,0-0.3,0-0.5C15.2,7.7,14.8,7.9,14.4,8z"/>
|
<path d="M14.4,8c0,3.5-2.9,6.4-6.4,6.4c-3.5,0-6.4-2.9-6.4-6.4c0-3.5,2.9-6.4,6.4-6.4c0.1-0.4,0.3-0.8,0.5-1.2c-0.2,0-0.3,0-0.5,0 C3.8,0.4,0.4,3.8,0.4,8c0,4.2,3.4,7.6,7.6,7.6s7.6-3.4,7.6-7.6c0-0.2,0-0.3,0-0.5C15.2,7.7,14.8,7.9,14.4,8z"/>
|
||||||
<circle cx="13" cy="3" r="3"/>
|
<circle cx="13" cy="3" r="3"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id='todo-check' viewBox="0 0 16 16">
|
||||||
|
<circle cx="8" cy="8" r="6" stroke="white" fill="none"/>
|
||||||
|
<path d="M5.33268 8L7.33268 10L10.666 6" stroke="white"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id='todo-uncheck' viewBox="0 0 16 16">
|
||||||
|
<circle cx="8" cy="8" r="6" stroke="white" fill="none"/>
|
||||||
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.6 KiB |
@ -17,6 +17,16 @@
|
|||||||
"More": "Options",
|
"More": "Options",
|
||||||
"TaskUnAssign": "Unassign",
|
"TaskUnAssign": "Unassign",
|
||||||
"NoTaskForObject": "No tasks defined",
|
"NoTaskForObject": "No tasks defined",
|
||||||
"Delete": "Delete"
|
"Delete": "Delete",
|
||||||
|
"NoTodoItems": "No to do's defined",
|
||||||
|
"TodoName": "Name",
|
||||||
|
"TodoState": "State",
|
||||||
|
"DoneState": "done",
|
||||||
|
"UndoneState": "todo",
|
||||||
|
"TodoDueDate": "Due to",
|
||||||
|
"TodoDescription": "To do description *",
|
||||||
|
"TodoEdit": "Edit To Do",
|
||||||
|
"TodoSave": "Save",
|
||||||
|
"TodoCreate": "Create To Do"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,7 +20,9 @@ const icons = require('../assets/icons.svg')
|
|||||||
loadMetadata(task.icon, {
|
loadMetadata(task.icon, {
|
||||||
Task: `${icons}#task`,
|
Task: `${icons}#task`,
|
||||||
Kanban: `${icons}#kanban`,
|
Kanban: `${icons}#kanban`,
|
||||||
Status: `${icons}#status`
|
Status: `${icons}#status`,
|
||||||
|
TodoCheck: `${icons}#todo-check`,
|
||||||
|
TodoUnCheck: `${icons}#todo-uncheck`
|
||||||
})
|
})
|
||||||
|
|
||||||
addStringsLoader(taskId, async (lang: string) => await import(`../lang/${lang}.json`))
|
addStringsLoader(taskId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
<!--
|
||||||
|
// 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 type { Class, Ref, Space } from '@anticrm/core'
|
||||||
|
import { Card, getClient } from '@anticrm/presentation'
|
||||||
|
import type { Task } from '@anticrm/task'
|
||||||
|
import { DatePicker, EditBox, Grid } from '@anticrm/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import task from '../../plugin'
|
||||||
|
|
||||||
|
export let objectId: Ref<Task>
|
||||||
|
export let _class: Ref<Class<Task>>
|
||||||
|
export let space: Ref<Space>
|
||||||
|
|
||||||
|
let name: string
|
||||||
|
const done = false
|
||||||
|
let dueTo: Date
|
||||||
|
|
||||||
|
$: _space = space
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
export function canClose (): boolean {
|
||||||
|
return objectId === undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTodo () {
|
||||||
|
await client.addCollection(
|
||||||
|
task.class.TodoItem,
|
||||||
|
space,
|
||||||
|
objectId,
|
||||||
|
_class,
|
||||||
|
'todos',
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
done,
|
||||||
|
dueTo: dueTo?.getTime() ?? undefined
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
label={task.string.TodoCreate}
|
||||||
|
okAction={createTodo}
|
||||||
|
canSave={name?.length > 0}
|
||||||
|
bind:space={_space}
|
||||||
|
on:close={() => {
|
||||||
|
dispatch('close')
|
||||||
|
}}
|
||||||
|
okLabel={task.string.TodoSave}>
|
||||||
|
<Grid column={1} rowGap={1.75}>
|
||||||
|
<EditBox
|
||||||
|
label={task.string.TodoDescription}
|
||||||
|
bind:value={name}
|
||||||
|
icon={task.icon.Task}
|
||||||
|
placeholder="todo..."
|
||||||
|
maxWidth="39rem"
|
||||||
|
focus
|
||||||
|
/>
|
||||||
|
<DatePicker title={task.string.TodoDueDate} bind:selected={dueTo} />
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
91
plugins/task-resources/src/components/todos/EditTodo.svelte
Normal file
91
plugins/task-resources/src/components/todos/EditTodo.svelte
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<!--
|
||||||
|
// 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 type { DocumentUpdate, Ref, Timestamp } from '@anticrm/core'
|
||||||
|
import { IntlString } from '@anticrm/platform'
|
||||||
|
import { Card, getClient } from '@anticrm/presentation'
|
||||||
|
import type { TodoItem } from '@anticrm/task'
|
||||||
|
import { DatePicker, EditBox, Grid } from '@anticrm/ui'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
import task from '../../plugin'
|
||||||
|
|
||||||
|
export let item: TodoItem
|
||||||
|
|
||||||
|
let name: string = ''
|
||||||
|
let dueTo: Date | undefined
|
||||||
|
|
||||||
|
let _itemId: Ref<TodoItem>
|
||||||
|
|
||||||
|
$: if (_itemId !== item._id) {
|
||||||
|
_itemId = item._id
|
||||||
|
name = item.name
|
||||||
|
dueTo = new Date(item.dueTo ?? 0)
|
||||||
|
console.log('AHTUNG', item, dueTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
export function canClose (): boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editTodo () {
|
||||||
|
const ops: DocumentUpdate<TodoItem> = {}
|
||||||
|
if (item.name !== name) {
|
||||||
|
ops.name = name
|
||||||
|
}
|
||||||
|
if (item.dueTo !== dueTo) {
|
||||||
|
ops.dueTo = (dueTo?.getTime() ?? null) as unknown as Timestamp
|
||||||
|
}
|
||||||
|
console.log('AHTUNG', ops)
|
||||||
|
|
||||||
|
if (Object.keys(ops).length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.updateCollection(
|
||||||
|
item._class,
|
||||||
|
item.space,
|
||||||
|
item._id,
|
||||||
|
item.attachedTo,
|
||||||
|
item.attachedToClass,
|
||||||
|
item.collection,
|
||||||
|
ops
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
label={task.string.TodoEdit}
|
||||||
|
okAction={editTodo}
|
||||||
|
canSave={name.length > 0}
|
||||||
|
space={item.space}
|
||||||
|
on:close={() => {
|
||||||
|
dispatch('close')
|
||||||
|
}}
|
||||||
|
okLabel={task.string.TodoSave}>
|
||||||
|
<Grid column={1} rowGap={1.75}>
|
||||||
|
<EditBox
|
||||||
|
label={task.string.TodoDescription}
|
||||||
|
bind:value={name}
|
||||||
|
icon={task.icon.Task}
|
||||||
|
placeholder="todo..."
|
||||||
|
maxWidth="39rem"
|
||||||
|
focus
|
||||||
|
/>
|
||||||
|
<DatePicker title={task.string.TodoDueDate} bind:selected={dueTo} />
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
|
// Copyright © 2021 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import type { TodoItem } from '@anticrm/task'
|
||||||
|
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
|
||||||
|
import task from '../../plugin'
|
||||||
|
import EditTodo from './EditTodo.svelte'
|
||||||
|
|
||||||
|
export let value: TodoItem
|
||||||
|
|
||||||
|
|
||||||
|
function show (elm: EventTarget | null) {
|
||||||
|
closeTooltip()
|
||||||
|
showPopup(EditTodo, { item: value }, elm as HTMLElement)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="sm-tool-icon" on:click={(evt) => show(evt.target)}>
|
||||||
|
<span class="icon"><Icon icon={task.icon.Task} size={'small'} /></span>{value.name}
|
||||||
|
</div>
|
@ -0,0 +1,47 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
|
// Copyright © 2021 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { TodoItem } from '@anticrm/task'
|
||||||
|
import { Label } from '@anticrm/ui'
|
||||||
|
import task from '../../plugin'
|
||||||
|
export let value: TodoItem
|
||||||
|
|
||||||
|
$: color = value.done ? '#60B96E' : '#6F7BC5'
|
||||||
|
$: text = value.done ? task.string.DoneState : task.string.UndoneState
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if value }
|
||||||
|
<div class="overflow-label state-container" style="background-color: {color};">
|
||||||
|
<Label label={text}/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.state-container {
|
||||||
|
padding: .25rem .5rem;
|
||||||
|
width: 6.25rem;
|
||||||
|
max-width: 6.25rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: .5px;
|
||||||
|
font-size: .625rem;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid rgba(0, 0, 0, .1);
|
||||||
|
border-radius: .25rem;
|
||||||
|
}
|
||||||
|
</style>
|
86
plugins/task-resources/src/components/todos/Todos.svelte
Normal file
86
plugins/task-resources/src/components/todos/Todos.svelte
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<!--
|
||||||
|
// 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 type { Ref, Space, Doc, Class } from '@anticrm/core'
|
||||||
|
import type { TodoItem } from '@anticrm/task'
|
||||||
|
import { createQuery } from '@anticrm/presentation'
|
||||||
|
import { CircleButton, IconAdd, showPopup, Label } from '@anticrm/ui'
|
||||||
|
import CreateTodo from './CreateTodo.svelte'
|
||||||
|
import { Table } from '@anticrm/view-resources'
|
||||||
|
|
||||||
|
import task from '../../plugin'
|
||||||
|
|
||||||
|
export let objectId: Ref<Doc>
|
||||||
|
export let space: Ref<Space>
|
||||||
|
export let _class: Ref<Class<Doc>>
|
||||||
|
|
||||||
|
let todos: TodoItem[] = []
|
||||||
|
|
||||||
|
const query = createQuery()
|
||||||
|
$: query.query(task.class.TodoItem, { attachedTo: objectId }, result => { todos = result })
|
||||||
|
|
||||||
|
const createApp = (ev: MouseEvent): void => {
|
||||||
|
showPopup(CreateTodo, { objectId, _class, space }, ev.target as HTMLElement)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="applications-container">
|
||||||
|
<div class="flex-row-center">
|
||||||
|
<div class="title">To Do's</div>
|
||||||
|
<CircleButton icon={IconAdd} size={'small'} selected on:click={createApp} />
|
||||||
|
</div>
|
||||||
|
{#if todos.length > 0}
|
||||||
|
<Table
|
||||||
|
_class={task.class.TodoItem}
|
||||||
|
config={[{ key: '', label: task.string.TodoName }, 'dueTo', { key: 'done', presenter: task.component.TodoStatePresenter, label: task.string.TodoState }]}
|
||||||
|
options={
|
||||||
|
{
|
||||||
|
// lookup: {
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
query={ { attachedTo: objectId } }
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<div class="flex-col-center mt-5 createapp-container">
|
||||||
|
<div class="small-text">
|
||||||
|
<a href={'#'} on:click={createApp}><Label label={task.string.NoTodoItems} /></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.applications-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-right: .75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.createapp-container {
|
||||||
|
padding: 1rem;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
background: var(--theme-bg-accent-color);
|
||||||
|
border: 1px solid var(--theme-bg-accent-color);
|
||||||
|
border-radius: .75rem;
|
||||||
|
}
|
||||||
|
</style>
|
@ -24,13 +24,17 @@ import TemplatesIcon from './components/TemplatesIcon.svelte'
|
|||||||
import EditIssue from './components/EditIssue.svelte'
|
import EditIssue from './components/EditIssue.svelte'
|
||||||
import { Doc } from '@anticrm/core'
|
import { Doc } from '@anticrm/core'
|
||||||
import { showPopup } from '@anticrm/ui'
|
import { showPopup } from '@anticrm/ui'
|
||||||
|
import { getClient } from '@anticrm/presentation'
|
||||||
|
|
||||||
import KanbanView from './components/kanban/KanbanView.svelte'
|
import KanbanView from './components/kanban/KanbanView.svelte'
|
||||||
import StateEditor from './components/state/StateEditor.svelte'
|
import StateEditor from './components/state/StateEditor.svelte'
|
||||||
import StatePresenter from './components/state/StatePresenter.svelte'
|
import StatePresenter from './components/state/StatePresenter.svelte'
|
||||||
import DoneStatePresenter from './components/state/DoneStatePresenter.svelte'
|
import DoneStatePresenter from './components/state/DoneStatePresenter.svelte'
|
||||||
import EditStatuses from './components/state/EditStatuses.svelte'
|
import EditStatuses from './components/state/EditStatuses.svelte'
|
||||||
import { SpaceWithStates } from '@anticrm/task'
|
import { SpaceWithStates, TodoItem } from '@anticrm/task'
|
||||||
|
import Todos from './components/todos/Todos.svelte'
|
||||||
|
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
|
||||||
|
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
|
||||||
|
|
||||||
export { default as KanbanTemplateEditor } from './components/kanban/KanbanTemplateEditor.svelte'
|
export { default as KanbanTemplateEditor } from './components/kanban/KanbanTemplateEditor.svelte'
|
||||||
export { default as KanbanTemplateSelector } from './components/kanban/KanbanTemplateSelector.svelte'
|
export { default as KanbanTemplateSelector } from './components/kanban/KanbanTemplateSelector.svelte'
|
||||||
@ -46,6 +50,19 @@ async function editStatuses (object: SpaceWithStates): Promise<void> {
|
|||||||
showPopup(EditStatuses, { _id: object._id, spaceClass: object._class }, 'right')
|
showPopup(EditStatuses, { _id: object._id, spaceClass: object._class }, 'right')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleDone (value: boolean, object: TodoItem): Promise<void> {
|
||||||
|
await getClient().updateCollection(
|
||||||
|
object._class,
|
||||||
|
object.space,
|
||||||
|
object._id,
|
||||||
|
object.attachedTo,
|
||||||
|
object.attachedToClass,
|
||||||
|
object.collection, {
|
||||||
|
done: value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default async (): Promise<Resources> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
component: {
|
component: {
|
||||||
CreateTask,
|
CreateTask,
|
||||||
@ -57,10 +74,15 @@ export default async (): Promise<Resources> => ({
|
|||||||
KanbanView,
|
KanbanView,
|
||||||
StatePresenter,
|
StatePresenter,
|
||||||
StateEditor,
|
StateEditor,
|
||||||
DoneStatePresenter
|
DoneStatePresenter,
|
||||||
|
Todos,
|
||||||
|
TodoItemPresenter,
|
||||||
|
TodoStatePresenter
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
CreateTask: createTask,
|
CreateTask: createTask,
|
||||||
EditStatuses: editStatuses
|
EditStatuses: editStatuses,
|
||||||
|
TodoItemMarkDone: async (obj: TodoItem) => await toggleDone(true, obj),
|
||||||
|
TodoItemMarkUnDone: async (obj: TodoItem) => await toggleDone(false, obj)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { IntlString, mergeIds } from '@anticrm/platform'
|
import { IntlString, mergeIds } from '@anticrm/platform'
|
||||||
|
|
||||||
import task, { taskId } from '@anticrm/task'
|
import task, { taskId } from '@anticrm/task'
|
||||||
|
import { AnyComponent } from '@anticrm/ui'
|
||||||
|
|
||||||
export default mergeIds(taskId, task, {
|
export default mergeIds(taskId, task, {
|
||||||
string: {
|
string: {
|
||||||
@ -35,9 +35,22 @@ export default mergeIds(taskId, task, {
|
|||||||
More: '' as IntlString,
|
More: '' as IntlString,
|
||||||
UploadDropFilesHere: '' as IntlString,
|
UploadDropFilesHere: '' as IntlString,
|
||||||
NoTaskForObject: '' as IntlString,
|
NoTaskForObject: '' as IntlString,
|
||||||
Delete: '' as IntlString
|
Delete: '' as IntlString,
|
||||||
|
NoTodoItems: '' as IntlString,
|
||||||
|
TodoName: '' as IntlString,
|
||||||
|
TodoState: '' as IntlString,
|
||||||
|
DoneState: '' as IntlString,
|
||||||
|
UndoneState: '' as IntlString,
|
||||||
|
TodoDueDate: '' as IntlString,
|
||||||
|
TodoDescription: '' as IntlString,
|
||||||
|
TodoEdit: '' as IntlString,
|
||||||
|
TodoSave: '' as IntlString,
|
||||||
|
TodoCreate: '' as IntlString
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
AssigneeRequired: '' as IntlString
|
AssigneeRequired: '' as IntlString
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
TodoStatePresenter: '' as AnyComponent
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import type { Employee } from '@anticrm/contact'
|
import type { Employee } from '@anticrm/contact'
|
||||||
import { AttachedDoc, Class, Client, Data, Doc, DocWithRank, genRanks, Mixin, Ref, Space, TxOperations } from '@anticrm/core'
|
import { AttachedDoc, Class, Client, Data, Doc, DocWithRank, genRanks, Mixin, Ref, Space, Timestamp, TxOperations } from '@anticrm/core'
|
||||||
import type { Asset, Plugin } from '@anticrm/platform'
|
import type { Asset, Plugin } from '@anticrm/platform'
|
||||||
import { plugin } from '@anticrm/platform'
|
import { plugin } from '@anticrm/platform'
|
||||||
import type { AnyComponent } from '@anticrm/ui'
|
import type { AnyComponent } from '@anticrm/ui'
|
||||||
@ -55,6 +55,17 @@ export interface Task extends AttachedDoc, DocWithRank {
|
|||||||
doneState: Ref<DoneState> | null
|
doneState: Ref<DoneState> | null
|
||||||
number: number
|
number: number
|
||||||
assignee: Ref<Employee> | null
|
assignee: Ref<Employee> | null
|
||||||
|
|
||||||
|
todoItems?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface TodoItem extends AttachedDoc {
|
||||||
|
name: string
|
||||||
|
done: boolean
|
||||||
|
dueTo?: Timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,7 +179,8 @@ const task = plugin(taskId, {
|
|||||||
WonStateTemplate: '' as Ref<Class<WonStateTemplate>>,
|
WonStateTemplate: '' as Ref<Class<WonStateTemplate>>,
|
||||||
LostStateTemplate: '' as Ref<Class<LostStateTemplate>>,
|
LostStateTemplate: '' as Ref<Class<LostStateTemplate>>,
|
||||||
KanbanTemplate: '' as Ref<Class<KanbanTemplate>>,
|
KanbanTemplate: '' as Ref<Class<KanbanTemplate>>,
|
||||||
KanbanTemplateSpace: '' as Ref<Class<KanbanTemplateSpace>>
|
KanbanTemplateSpace: '' as Ref<Class<KanbanTemplateSpace>>,
|
||||||
|
TodoItem: '' as Ref<Class<TodoItem>>
|
||||||
},
|
},
|
||||||
viewlet: {
|
viewlet: {
|
||||||
Kanban: '' as Ref<ViewletDescriptor>
|
Kanban: '' as Ref<ViewletDescriptor>
|
||||||
@ -176,7 +188,9 @@ const task = plugin(taskId, {
|
|||||||
icon: {
|
icon: {
|
||||||
Task: '' as Asset,
|
Task: '' as Asset,
|
||||||
Kanban: '' as Asset,
|
Kanban: '' as Asset,
|
||||||
Status: '' as Asset
|
Status: '' as Asset,
|
||||||
|
TodoCheck: '' as Asset,
|
||||||
|
TodoUnCheck: '' as Asset
|
||||||
},
|
},
|
||||||
global: {
|
global: {
|
||||||
// Global task root, if not attached to some other object.
|
// Global task root, if not attached to some other object.
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
import { getResource } from '@anticrm/platform'
|
import { getResource } from '@anticrm/platform'
|
||||||
import { getClient } from '@anticrm/presentation'
|
import { getClient } from '@anticrm/presentation'
|
||||||
import { Menu } from '@anticrm/ui'
|
import { Menu } from '@anticrm/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import { getActions } from '../utils'
|
import { getActions } from '../utils'
|
||||||
|
|
||||||
export let object: Doc
|
export let object: Doc
|
||||||
@ -31,15 +30,13 @@
|
|||||||
}[] = []
|
}[] = []
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
async function invokeAction (action: Resource<(object: Doc) => Promise<void>>) {
|
async function invokeAction (action: Resource<(object: Doc) => Promise<void>>) {
|
||||||
dispatch('close')
|
|
||||||
const impl = await getResource(action)
|
const impl = await getResource(action)
|
||||||
await impl(object)
|
await impl(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
getActions(client, object._class).then(result => {
|
getActions(client, object).then(result => {
|
||||||
actions = result.map(a => ({
|
actions = result.map(a => ({
|
||||||
label: a.label,
|
label: a.label,
|
||||||
icon: a.icon,
|
icon: a.icon,
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
import { SortingOrder } from '@anticrm/core'
|
import { SortingOrder } from '@anticrm/core'
|
||||||
import { createQuery, getClient } from '@anticrm/presentation'
|
import { createQuery, getClient } from '@anticrm/presentation'
|
||||||
import { IconDown, IconUp, Label, Loading, showPopup } from '@anticrm/ui'
|
import { IconDown, IconUp, Label, Loading, showPopup } from '@anticrm/ui'
|
||||||
|
import { BuildModelKey } from '@anticrm/view'
|
||||||
import { buildModel } from '../utils'
|
import { buildModel } from '../utils'
|
||||||
import MoreV from './icons/MoreV.svelte'
|
import MoreV from './icons/MoreV.svelte'
|
||||||
import Menu from './Menu.svelte'
|
import Menu from './Menu.svelte'
|
||||||
@ -26,7 +27,7 @@
|
|||||||
export let _class: Ref<Class<Doc>>
|
export let _class: Ref<Class<Doc>>
|
||||||
export let query: DocumentQuery<Doc>
|
export let query: DocumentQuery<Doc>
|
||||||
export let options: FindOptions<Doc> | undefined
|
export let options: FindOptions<Doc> | undefined
|
||||||
export let config: string[]
|
export let config: (BuildModelKey|string)[]
|
||||||
|
|
||||||
let sortKey = 'modifiedOn'
|
let sortKey = 'modifiedOn'
|
||||||
let sortOrder = SortingOrder.Descending
|
let sortOrder = SortingOrder.Descending
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import core, { AttachedDoc, Class, Client, Collection, Doc, FindOptions, FindResult, Obj, Ref, TxOperations } from '@anticrm/core'
|
import core, { AttachedDoc, Class, Client, Collection, Doc, FindOptions, FindResult, Obj, Ref, TxOperations, matchQuery } from '@anticrm/core'
|
||||||
import type { IntlString } from '@anticrm/platform'
|
import type { IntlString } from '@anticrm/platform'
|
||||||
import { getResource } from '@anticrm/platform'
|
import { getResource } from '@anticrm/platform'
|
||||||
import { getAttributePresenterClass } from '@anticrm/presentation'
|
import { getAttributePresenterClass } from '@anticrm/presentation'
|
||||||
@ -41,9 +41,9 @@ export async function getObjectPresenter (client: Client, _class: Ref<Class<Obj>
|
|||||||
(key.length > 0 ? key + '.' + clazz.sortingKey : clazz.sortingKey)
|
(key.length > 0 ? key + '.' + clazz.sortingKey : clazz.sortingKey)
|
||||||
: key
|
: key
|
||||||
return {
|
return {
|
||||||
key,
|
key: preserveKey.key,
|
||||||
_class,
|
_class,
|
||||||
label: clazz.label,
|
label: preserveKey.label ?? clazz.label,
|
||||||
presenter,
|
presenter,
|
||||||
sortingKey
|
sortingKey
|
||||||
}
|
}
|
||||||
@ -68,16 +68,16 @@ async function getAttributePresenter (client: Client, _class: Ref<Class<Obj>>, k
|
|||||||
const sortingKey = attribute.type._class === core.class.ArrOf ? resultKey + '.length' : resultKey
|
const sortingKey = attribute.type._class === core.class.ArrOf ? resultKey + '.length' : resultKey
|
||||||
const presenter = await getResource(presenterMixin.presenter)
|
const presenter = await getResource(presenterMixin.presenter)
|
||||||
return {
|
return {
|
||||||
key: resultKey,
|
key: preserveKey.key,
|
||||||
sortingKey,
|
sortingKey,
|
||||||
_class: attrClass,
|
_class: attrClass,
|
||||||
label: attribute.label,
|
label: preserveKey.label ?? attribute.label,
|
||||||
presenter
|
presenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: BuildModelKey, preserveKey: BuildModelKey, options?: FindOptions<Doc>): Promise<AttributeModel> {
|
async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: BuildModelKey, preserveKey: BuildModelKey, options?: FindOptions<Doc>): Promise<AttributeModel> {
|
||||||
if (typeof key === 'object') {
|
if (key.presenter !== undefined) {
|
||||||
const { presenter, label, sortingKey } = key
|
const { presenter, label, sortingKey } = key
|
||||||
return {
|
return {
|
||||||
key: '',
|
key: '',
|
||||||
@ -87,41 +87,41 @@ async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: Build
|
|||||||
presenter: await getResource(presenter)
|
presenter: await getResource(presenter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key.length === 0) {
|
if (key.key.length === 0) {
|
||||||
return await getObjectPresenter(client, _class, preserveKey)
|
return await getObjectPresenter(client, _class, preserveKey)
|
||||||
} else {
|
} else {
|
||||||
const split = key.split('.')
|
const split = key.key.split('.')
|
||||||
if (split[0] === '$lookup') {
|
if (split[0] === '$lookup') {
|
||||||
const lookupClass = (options?.lookup as any)[split[1]] as Ref<Class<Obj>>
|
const lookupClass = (options?.lookup as any)[split[1]] as Ref<Class<Obj>>
|
||||||
if (lookupClass === undefined) {
|
if (lookupClass === undefined) {
|
||||||
throw new Error('lookup class does not provided for ' + split[1])
|
throw new Error('lookup class does not provided for ' + split[1])
|
||||||
}
|
}
|
||||||
const lookupKey = split[2] ?? ''
|
const lookupKey = { ...key, key: split[2] ?? '' }
|
||||||
const model = await getPresenter(client, lookupClass, lookupKey, preserveKey)
|
const model = await getPresenter(client, lookupClass, lookupKey, preserveKey)
|
||||||
if (lookupKey === '') {
|
if (lookupKey.key === '') {
|
||||||
const attribute = client.getHierarchy().getAttribute(_class, split[1])
|
const attribute = client.getHierarchy().getAttribute(_class, split[1])
|
||||||
model.label = attribute.label
|
model.label = attribute.label
|
||||||
} else {
|
} else {
|
||||||
const attribute = client.getHierarchy().getAttribute(lookupClass, lookupKey)
|
const attribute = client.getHierarchy().getAttribute(lookupClass, lookupKey.key)
|
||||||
model.label = attribute.label
|
model.label = attribute.label
|
||||||
}
|
}
|
||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
return await getAttributePresenter(client, _class, key, preserveKey)
|
return await getAttributePresenter(client, _class, key.key, preserveKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildModel (options: BuildModelOptions): Promise<AttributeModel[]> {
|
export async function buildModel (options: BuildModelOptions): Promise<AttributeModel[]> {
|
||||||
console.log('building table model for', options)
|
console.log('building table model for', options)
|
||||||
// eslint-disable-next-line array-callback-return
|
// eslint-disable-next-line array-callback-return
|
||||||
const model = options.keys.map(async key => {
|
const model = options.keys.map(key => typeof key === 'string' ? { key: key } : key).map(async key => {
|
||||||
try {
|
try {
|
||||||
return await getPresenter(options.client, options._class, key, key, options.options)
|
return await getPresenter(options.client, options._class, key, key, options.options)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if ((options.ignoreMissing ?? false)) {
|
if ((options.ignoreMissing ?? false)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const stringKey = (typeof key === 'string') ? key : key.label
|
const stringKey = key.label ?? key.key
|
||||||
console.error('Failed to find presenter for', key, err)
|
console.error('Failed to find presenter for', key, err)
|
||||||
const errorPresenter: AttributeModel = {
|
const errorPresenter: AttributeModel = {
|
||||||
key: '',
|
key: '',
|
||||||
@ -138,11 +138,17 @@ export async function buildModel (options: BuildModelOptions): Promise<Attribute
|
|||||||
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
|
return (await Promise.all(model)).filter(a => a !== undefined) as AttributeModel[]
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterActions (client: Client, _class: Ref<Class<Obj>>, targets: ActionTarget[], derived: Ref<Class<Doc>> = core.class.Doc): Array<Ref<Action>> {
|
function filterActions (client: Client, doc: Doc, targets: ActionTarget[], derived: Ref<Class<Doc>> = core.class.Doc): Array<Ref<Action>> {
|
||||||
const result: Array<Ref<Action>> = []
|
const result: Array<Ref<Action>> = []
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
if (hierarchy.isDerived(_class, target.target) && client.getHierarchy().isDerived(target.target, derived)) {
|
if (target.query !== undefined) {
|
||||||
|
const r = matchQuery([doc], target.query)
|
||||||
|
if (r.length === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hierarchy.isDerived(doc._class, target.target) && client.getHierarchy().isDerived(target.target, derived)) {
|
||||||
result.push(target.action)
|
result.push(target.action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,9 +163,9 @@ function filterActions (client: Client, _class: Ref<Class<Obj>>, targets: Action
|
|||||||
* So if we have contribution for Doc, Space and we ask for SpaceWithStates and derivedFrom=Space,
|
* So if we have contribution for Doc, Space and we ask for SpaceWithStates and derivedFrom=Space,
|
||||||
* we won't recieve Doc contribution but recieve Space ones.
|
* we won't recieve Doc contribution but recieve Space ones.
|
||||||
*/
|
*/
|
||||||
export async function getActions (client: Client, _class: Ref<Class<Obj>>, derived: Ref<Class<Doc>> = core.class.Doc): Promise<FindResult<Action>> {
|
export async function getActions (client: Client, doc: Doc, derived: Ref<Class<Doc>> = core.class.Doc): Promise<FindResult<Action>> {
|
||||||
const targets = await client.findAll(view.class.ActionTarget, {})
|
const targets = await client.findAll(view.class.ActionTarget, {})
|
||||||
return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, _class, targets, derived) } })
|
return await client.findAll(view.class.Action, { _id: { $in: filterActions(client, doc, targets, derived) } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteObject (client: Client & TxOperations, object: Doc): Promise<void> {
|
export async function deleteObject (client: Client & TxOperations, object: Doc): Promise<void> {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Class, Client, Doc, FindOptions, Mixin, Obj, Ref, Space, UXObject } from '@anticrm/core'
|
import type { Class, Client, Doc, DocumentQuery, FindOptions, Mixin, Obj, Ref, Space, UXObject } from '@anticrm/core'
|
||||||
import type { Asset, IntlString, Plugin, Resource, Status } from '@anticrm/platform'
|
import type { Asset, IntlString, Plugin, Resource, Status } from '@anticrm/platform'
|
||||||
import { plugin } from '@anticrm/platform'
|
import { plugin } from '@anticrm/platform'
|
||||||
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
|
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
|
||||||
@ -75,9 +75,11 @@ export interface Action extends Doc, UXObject {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface ActionTarget extends Doc {
|
export interface ActionTarget<T extends Doc=Doc> extends Doc {
|
||||||
target: Ref<Class<Doc>>
|
target: Ref<Class<T>>
|
||||||
action: Ref<Action>
|
action: Ref<Action>
|
||||||
|
|
||||||
|
query?: DocumentQuery<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,9 +90,10 @@ export const viewId = 'view' as Plugin
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type BuildModelKey = string | {
|
export interface BuildModelKey {
|
||||||
presenter: AnyComponent
|
key: string
|
||||||
label: string
|
presenter?: AnyComponent
|
||||||
|
label?: IntlString
|
||||||
sortingKey?: string
|
sortingKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +116,7 @@ export interface AttributeModel {
|
|||||||
export interface BuildModelOptions {
|
export interface BuildModelOptions {
|
||||||
client: Client
|
client: Client
|
||||||
_class: Ref<Class<Obj>>
|
_class: Ref<Class<Obj>>
|
||||||
keys: BuildModelKey[]
|
keys: (BuildModelKey | string)[]
|
||||||
options?: FindOptions<Doc>
|
options?: FindOptions<Doc>
|
||||||
ignoreMissing?: boolean
|
ignoreMissing?: boolean
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
async function getActions (space: Space): Promise<Action[]> {
|
async function getActions (space: Space): Promise<Action[]> {
|
||||||
const result = [editSpace]
|
const result = [editSpace]
|
||||||
|
|
||||||
const extraActions = await getContributedActions(client, space._class, core.class.Space)
|
const extraActions = await getContributedActions(client, space, core.class.Space)
|
||||||
for (const act of extraActions) {
|
for (const act of extraActions) {
|
||||||
result.push({
|
result.push({
|
||||||
icon: act.icon ?? IconEdit,
|
icon: act.icon ?? IconEdit,
|
||||||
|
Loading…
Reference in New Issue
Block a user