mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-22 00:10:37 +00:00
parent
ec738daf75
commit
56380c4958
@ -14,13 +14,13 @@
|
||||
//
|
||||
|
||||
// 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 type { Employee } from '@anticrm/contact'
|
||||
import contact from '@anticrm/contact'
|
||||
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space } from '@anticrm/core'
|
||||
import { Builder, Collection, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import { Builder, Collection, Mixin, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
|
||||
import view from '@anticrm/model-view'
|
||||
@ -42,7 +42,8 @@ import type {
|
||||
WonStateTemplate,
|
||||
LostStateTemplate,
|
||||
KanbanTemplate,
|
||||
Task
|
||||
Task,
|
||||
TodoItem
|
||||
} from '@anticrm/task'
|
||||
import { createProjectKanban } from '@anticrm/task'
|
||||
import task from './plugin'
|
||||
@ -99,6 +100,22 @@ export class TTask extends TAttachedDoc implements Task {
|
||||
assignee!: Ref<Employee> | null
|
||||
|
||||
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)
|
||||
@ -212,7 +229,8 @@ export function createModel (builder: Builder): void {
|
||||
TTask,
|
||||
TSpaceWithStates,
|
||||
TProject,
|
||||
TIssue
|
||||
TIssue,
|
||||
TTodoItem
|
||||
)
|
||||
builder.mixin(task.class.Project, core.class.Class, workbench.mixin.SpaceView, {
|
||||
view: {
|
||||
@ -389,6 +407,51 @@ export function createModel (builder: Builder): void {
|
||||
builder.mixin(task.class.DoneState, core.class.Class, view.mixin.AttributePresenter, {
|
||||
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'
|
||||
|
@ -28,11 +28,15 @@ export default mergeIds(taskId, task, {
|
||||
},
|
||||
action: {
|
||||
CreateTask: '' as Ref<Action>,
|
||||
EditStatuses: '' as Ref<Action>
|
||||
EditStatuses: '' as Ref<Action>,
|
||||
TodoItemMarkDone: '' as Ref<Action>,
|
||||
TodoItemMarkUnDone: '' as Ref<Action>
|
||||
},
|
||||
actionImpl: {
|
||||
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: {
|
||||
ProjectView: '' as AnyComponent,
|
||||
@ -46,7 +50,9 @@ export default mergeIds(taskId, task, {
|
||||
StatePresenter: '' as AnyComponent,
|
||||
DoneStatePresenter: '' as AnyComponent,
|
||||
StateEditor: '' as AnyComponent,
|
||||
KanbanView: '' as AnyComponent
|
||||
KanbanView: '' as AnyComponent,
|
||||
Todos: '' as AnyComponent,
|
||||
TodoItemPresenter: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Task: '' as IntlString,
|
||||
|
@ -18,7 +18,7 @@ import clone from 'just-clone'
|
||||
import type { Class, Doc, Ref } from './classes'
|
||||
import core from './component'
|
||||
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 { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||
import { TxProcessor } from './tx'
|
||||
@ -108,11 +108,7 @@ export abstract class MemDb extends TxProcessor {
|
||||
result = this.getObjectsByClass(_class)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
result = matchQuery(result, query)
|
||||
|
||||
if (options?.lookup !== undefined) result = this.lookup(result as T[], options.lookup)
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { DocumentQuery } from '.'
|
||||
import { Doc } from './classes'
|
||||
import { createPredicates, isPredicate } from './predicate'
|
||||
import { SortingQuery } from './storage'
|
||||
@ -102,3 +103,18 @@ function getValue (key: string, obj: any): any {
|
||||
}
|
||||
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 presentation from '..'
|
||||
|
||||
export let spaceClass: Ref<Class<Space>>
|
||||
export let spaceClass: Ref<Class<Space>> | undefined = undefined
|
||||
export let space: Ref<Space>
|
||||
export let spaceLabel: IntlString
|
||||
export let spacePlaceholder: IntlString
|
||||
export let spaceLabel: IntlString | undefined = undefined
|
||||
export let spacePlaceholder: IntlString | undefined = undefined
|
||||
export let label: IntlString
|
||||
export let okAction: () => void
|
||||
export let canSave: boolean = false
|
||||
|
||||
export let okLabel: IntlString = presentation.string.Create
|
||||
export let cancelLabel: IntlString = presentation.string.Cancel
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
@ -48,13 +51,15 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="content"><slot /></div>
|
||||
{#if spaceClass && spaceLabel && spacePlaceholder}
|
||||
<div class="flex-col pool">
|
||||
<div class="separator" />
|
||||
<SpaceSelect _class={spaceClass} label={spaceLabel} placeholder={spacePlaceholder} bind:value={space} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="footer">
|
||||
<Button disabled={!canSave} label={presentation.string.Create} size={'small'} transparent primary on:click={() => { okAction(); dispatch('close') }} />
|
||||
<Button label={presentation.string.Cancel} size={'small'} transparent on:click={() => { dispatch('close') }} />
|
||||
<Button disabled={!canSave} label={okLabel} size={'small'} transparent primary on:click={() => { okAction(); dispatch('close') }} />
|
||||
<Button label={cancelLabel} size={'small'} transparent on:click={() => { dispatch('close') }} />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
@ -33,7 +33,7 @@
|
||||
showPopup,
|
||||
TimeSince
|
||||
} 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 { activityKey, ActivityKey, DisplayTx } from '../activity'
|
||||
import ShowMore from './ShowMore.svelte'
|
||||
@ -53,7 +53,6 @@
|
||||
let props: any
|
||||
let employee: EmployeeAccount | undefined
|
||||
let model: AttributeModel[] = []
|
||||
let actions: Action[] = []
|
||||
|
||||
let edit = false
|
||||
|
||||
@ -74,7 +73,7 @@
|
||||
}
|
||||
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) {
|
||||
return {
|
||||
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> {
|
||||
const val = (utx.operations as any)[m.key]
|
||||
console.log(m._class, m.key, val, typeof val)
|
||||
@ -140,6 +135,7 @@
|
||||
return val
|
||||
}
|
||||
const showMenu = async (ev: MouseEvent): Promise<void> => {
|
||||
const actions = await getActions(client, tx.doc as Doc)
|
||||
showPopup(
|
||||
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"/>
|
||||
<circle cx="13" cy="3" r="3"/>
|
||||
</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>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.6 KiB |
@ -17,6 +17,16 @@
|
||||
"More": "Options",
|
||||
"TaskUnAssign": "Unassign",
|
||||
"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, {
|
||||
Task: `${icons}#task`,
|
||||
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`))
|
||||
|
@ -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 { Doc } from '@anticrm/core'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
|
||||
import KanbanView from './components/kanban/KanbanView.svelte'
|
||||
import StateEditor from './components/state/StateEditor.svelte'
|
||||
import StatePresenter from './components/state/StatePresenter.svelte'
|
||||
import DoneStatePresenter from './components/state/DoneStatePresenter.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 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')
|
||||
}
|
||||
|
||||
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> => ({
|
||||
component: {
|
||||
CreateTask,
|
||||
@ -57,10 +74,15 @@ export default async (): Promise<Resources> => ({
|
||||
KanbanView,
|
||||
StatePresenter,
|
||||
StateEditor,
|
||||
DoneStatePresenter
|
||||
DoneStatePresenter,
|
||||
Todos,
|
||||
TodoItemPresenter,
|
||||
TodoStatePresenter
|
||||
},
|
||||
actionImpl: {
|
||||
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 task, { taskId } from '@anticrm/task'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
|
||||
export default mergeIds(taskId, task, {
|
||||
string: {
|
||||
@ -35,9 +35,22 @@ export default mergeIds(taskId, task, {
|
||||
More: '' as IntlString,
|
||||
UploadDropFilesHere: '' 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: {
|
||||
AssigneeRequired: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
TodoStatePresenter: '' as AnyComponent
|
||||
}
|
||||
})
|
||||
|
@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
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 { plugin } from '@anticrm/platform'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
@ -55,6 +55,17 @@ export interface Task extends AttachedDoc, DocWithRank {
|
||||
doneState: Ref<DoneState> | null
|
||||
number: number
|
||||
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>>,
|
||||
LostStateTemplate: '' as Ref<Class<LostStateTemplate>>,
|
||||
KanbanTemplate: '' as Ref<Class<KanbanTemplate>>,
|
||||
KanbanTemplateSpace: '' as Ref<Class<KanbanTemplateSpace>>
|
||||
KanbanTemplateSpace: '' as Ref<Class<KanbanTemplateSpace>>,
|
||||
TodoItem: '' as Ref<Class<TodoItem>>
|
||||
},
|
||||
viewlet: {
|
||||
Kanban: '' as Ref<ViewletDescriptor>
|
||||
@ -176,7 +188,9 @@ const task = plugin(taskId, {
|
||||
icon: {
|
||||
Task: '' as Asset,
|
||||
Kanban: '' as Asset,
|
||||
Status: '' as Asset
|
||||
Status: '' as Asset,
|
||||
TodoCheck: '' as Asset,
|
||||
TodoUnCheck: '' as Asset
|
||||
},
|
||||
global: {
|
||||
// Global task root, if not attached to some other object.
|
||||
|
@ -19,7 +19,6 @@
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Menu } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getActions } from '../utils'
|
||||
|
||||
export let object: Doc
|
||||
@ -31,15 +30,13 @@
|
||||
}[] = []
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function invokeAction (action: Resource<(object: Doc) => Promise<void>>) {
|
||||
dispatch('close')
|
||||
const impl = await getResource(action)
|
||||
await impl(object)
|
||||
}
|
||||
|
||||
getActions(client, object._class).then(result => {
|
||||
getActions(client, object).then(result => {
|
||||
actions = result.map(a => ({
|
||||
label: a.label,
|
||||
icon: a.icon,
|
||||
|
@ -19,6 +19,7 @@
|
||||
import { SortingOrder } from '@anticrm/core'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { IconDown, IconUp, Label, Loading, showPopup } from '@anticrm/ui'
|
||||
import { BuildModelKey } from '@anticrm/view'
|
||||
import { buildModel } from '../utils'
|
||||
import MoreV from './icons/MoreV.svelte'
|
||||
import Menu from './Menu.svelte'
|
||||
@ -26,7 +27,7 @@
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let query: DocumentQuery<Doc>
|
||||
export let options: FindOptions<Doc> | undefined
|
||||
export let config: string[]
|
||||
export let config: (BuildModelKey|string)[]
|
||||
|
||||
let sortKey = 'modifiedOn'
|
||||
let sortOrder = SortingOrder.Descending
|
||||
|
@ -14,7 +14,7 @@
|
||||
// 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 { getResource } from '@anticrm/platform'
|
||||
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
|
||||
return {
|
||||
key,
|
||||
key: preserveKey.key,
|
||||
_class,
|
||||
label: clazz.label,
|
||||
label: preserveKey.label ?? clazz.label,
|
||||
presenter,
|
||||
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 presenter = await getResource(presenterMixin.presenter)
|
||||
return {
|
||||
key: resultKey,
|
||||
key: preserveKey.key,
|
||||
sortingKey,
|
||||
_class: attrClass,
|
||||
label: attribute.label,
|
||||
label: preserveKey.label ?? attribute.label,
|
||||
presenter
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
return {
|
||||
key: '',
|
||||
@ -87,41 +87,41 @@ async function getPresenter (client: Client, _class: Ref<Class<Obj>>, key: Build
|
||||
presenter: await getResource(presenter)
|
||||
}
|
||||
}
|
||||
if (key.length === 0) {
|
||||
if (key.key.length === 0) {
|
||||
return await getObjectPresenter(client, _class, preserveKey)
|
||||
} else {
|
||||
const split = key.split('.')
|
||||
const split = key.key.split('.')
|
||||
if (split[0] === '$lookup') {
|
||||
const lookupClass = (options?.lookup as any)[split[1]] as Ref<Class<Obj>>
|
||||
if (lookupClass === undefined) {
|
||||
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)
|
||||
if (lookupKey === '') {
|
||||
if (lookupKey.key === '') {
|
||||
const attribute = client.getHierarchy().getAttribute(_class, split[1])
|
||||
model.label = attribute.label
|
||||
} else {
|
||||
const attribute = client.getHierarchy().getAttribute(lookupClass, lookupKey)
|
||||
const attribute = client.getHierarchy().getAttribute(lookupClass, lookupKey.key)
|
||||
model.label = attribute.label
|
||||
}
|
||||
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[]> {
|
||||
console.log('building table model for', options)
|
||||
// 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 {
|
||||
return await getPresenter(options.client, options._class, key, key, options.options)
|
||||
} catch (err: any) {
|
||||
if ((options.ignoreMissing ?? false)) {
|
||||
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)
|
||||
const errorPresenter: AttributeModel = {
|
||||
key: '',
|
||||
@ -138,11 +138,17 @@ export async function buildModel (options: BuildModelOptions): Promise<Attribute
|
||||
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 hierarchy = client.getHierarchy()
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
* 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, {})
|
||||
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> {
|
||||
|
@ -14,7 +14,7 @@
|
||||
// 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 { plugin } from '@anticrm/platform'
|
||||
import type { AnyComponent, AnySvelteComponent } from '@anticrm/ui'
|
||||
@ -75,9 +75,11 @@ export interface Action extends Doc, UXObject {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ActionTarget extends Doc {
|
||||
target: Ref<Class<Doc>>
|
||||
export interface ActionTarget<T extends Doc=Doc> extends Doc {
|
||||
target: Ref<Class<T>>
|
||||
action: Ref<Action>
|
||||
|
||||
query?: DocumentQuery<T>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,9 +90,10 @@ export const viewId = 'view' as Plugin
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type BuildModelKey = string | {
|
||||
presenter: AnyComponent
|
||||
label: string
|
||||
export interface BuildModelKey {
|
||||
key: string
|
||||
presenter?: AnyComponent
|
||||
label?: IntlString
|
||||
sortingKey?: string
|
||||
}
|
||||
|
||||
@ -113,7 +116,7 @@ export interface AttributeModel {
|
||||
export interface BuildModelOptions {
|
||||
client: Client
|
||||
_class: Ref<Class<Obj>>
|
||||
keys: BuildModelKey[]
|
||||
keys: (BuildModelKey | string)[]
|
||||
options?: FindOptions<Doc>
|
||||
ignoreMissing?: boolean
|
||||
}
|
||||
|
@ -67,7 +67,7 @@
|
||||
async function getActions (space: Space): Promise<Action[]> {
|
||||
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) {
|
||||
result.push({
|
||||
icon: act.icon ?? IconEdit,
|
||||
|
Loading…
Reference in New Issue
Block a user