mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-28 23:27:38 +03:00
Use list for sub issues (#2514)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
1e14668439
commit
28f3e6169c
1
.gitignore
vendored
1
.gitignore
vendored
@ -78,3 +78,4 @@ tsconfig.tsbuildinfo
|
|||||||
ingest-attachment-*.zip
|
ingest-attachment-*.zip
|
||||||
tsdoc-metadata.json
|
tsdoc-metadata.json
|
||||||
pods/front/dist
|
pods/front/dist
|
||||||
|
*.cpuprofile
|
||||||
|
@ -550,6 +550,74 @@ export function createModel (builder: Builder): void {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const subIssuesOptions: ViewOptionsModel = {
|
||||||
|
groupBy: ['status', 'assignee', 'priority', 'sprint'],
|
||||||
|
orderBy: [
|
||||||
|
['status', SortingOrder.Ascending],
|
||||||
|
['priority', SortingOrder.Ascending],
|
||||||
|
['modifiedOn', SortingOrder.Descending],
|
||||||
|
['dueDate', SortingOrder.Descending],
|
||||||
|
['rank', SortingOrder.Ascending]
|
||||||
|
],
|
||||||
|
other: []
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.createDoc(
|
||||||
|
view.class.Viewlet,
|
||||||
|
core.space.Model,
|
||||||
|
{
|
||||||
|
attachTo: tracker.class.Issue,
|
||||||
|
descriptor: view.viewlet.List,
|
||||||
|
viewOptions: subIssuesOptions,
|
||||||
|
variant: 'subissue',
|
||||||
|
config: [
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.PriorityEditor,
|
||||||
|
props: { type: 'priority', kind: 'list', size: 'small' }
|
||||||
|
},
|
||||||
|
{ key: '', presenter: tracker.component.IssuePresenter, props: { type: 'issue', fixed: 'left' } },
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.StatusEditor,
|
||||||
|
props: { kind: 'list', size: 'small', justify: 'center' }
|
||||||
|
},
|
||||||
|
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true, showParent: false } },
|
||||||
|
{ key: '', presenter: tracker.component.SubIssuesSelector, props: {} },
|
||||||
|
{ key: '', presenter: view.component.GrowPresenter, props: { type: 'grow' } },
|
||||||
|
{ key: '', presenter: tracker.component.DueDatePresenter, props: { kind: 'list' } },
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.SprintEditor,
|
||||||
|
props: {
|
||||||
|
kind: 'list',
|
||||||
|
size: 'small',
|
||||||
|
shape: 'round',
|
||||||
|
shouldShowPlaceholder: false,
|
||||||
|
excludeByKey: 'sprint',
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
presenter: tracker.component.EstimationEditor,
|
||||||
|
props: { kind: 'list', size: 'small', optional: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'modifiedOn',
|
||||||
|
presenter: tracker.component.ModificationDatePresenter,
|
||||||
|
props: { fixed: 'right', optional: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '$lookup.assignee',
|
||||||
|
presenter: tracker.component.AssigneePresenter,
|
||||||
|
props: { issueClass: tracker.class.Issue, defaultClass: contact.class.Employee, shouldShowLabel: false }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
tracker.viewlet.SubIssues
|
||||||
|
)
|
||||||
|
|
||||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||||
attachTo: tracker.class.IssueTemplate,
|
attachTo: tracker.class.IssueTemplate,
|
||||||
descriptor: view.viewlet.List,
|
descriptor: view.viewlet.List,
|
||||||
|
@ -63,8 +63,7 @@ export default mergeIds(viewId, view, {
|
|||||||
HTMLEditor: '' as AnyComponent,
|
HTMLEditor: '' as AnyComponent,
|
||||||
MarkupEditor: '' as AnyComponent,
|
MarkupEditor: '' as AnyComponent,
|
||||||
MarkupEditorPopup: '' as AnyComponent,
|
MarkupEditorPopup: '' as AnyComponent,
|
||||||
ListView: '' as AnyComponent,
|
ListView: '' as AnyComponent
|
||||||
GrowPresenter: '' as AnyComponent
|
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
Table: '' as IntlString,
|
Table: '' as IntlString,
|
||||||
|
@ -20,6 +20,7 @@ import core from './component'
|
|||||||
import { _createMixinProxy, _mixinClass, _toDoc } from './proxy'
|
import { _createMixinProxy, _mixinClass, _toDoc } from './proxy'
|
||||||
import type { Tx, TxCreateDoc, TxMixin, TxRemoveDoc, TxUpdateDoc } from './tx'
|
import type { Tx, TxCreateDoc, TxMixin, TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||||
import { TxProcessor } from './tx'
|
import { TxProcessor } from './tx'
|
||||||
|
import getTypeOf from './typeof'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -463,11 +464,12 @@ export class Hierarchy {
|
|||||||
if (typeof obj === 'function') {
|
if (typeof obj === 'function') {
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
const result: any = Array.isArray(obj) ? [] : {}
|
const isArray = Array.isArray(obj)
|
||||||
|
const result: any = isArray ? [] : Object.assign({}, obj)
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
// include prototype properties
|
// include prototype properties
|
||||||
const value = obj[key]
|
const value = obj[key]
|
||||||
const type = {}.toString.call(value).slice(8, -1)
|
const type = getTypeOf(value)
|
||||||
if (type === 'Array') {
|
if (type === 'Array') {
|
||||||
result[key] = this.clone(value)
|
result[key] = this.clone(value)
|
||||||
} else if (type === 'Object') {
|
} else if (type === 'Object') {
|
||||||
@ -477,7 +479,9 @@ export class Hierarchy {
|
|||||||
} else if (type === 'Date') {
|
} else if (type === 'Date') {
|
||||||
result[key] = new Date(value.getTime())
|
result[key] = new Date(value.getTime())
|
||||||
} else {
|
} else {
|
||||||
result[key] = value
|
if (isArray) {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
35
packages/core/src/typeof.ts
Normal file
35
packages/core/src/typeof.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const se = typeof Symbol !== 'undefined'
|
||||||
|
const ste = se && typeof Symbol.toStringTag !== 'undefined'
|
||||||
|
|
||||||
|
export default function getTypeOf (obj: any): string {
|
||||||
|
const typeofObj = typeof obj
|
||||||
|
if (typeofObj !== 'object') {
|
||||||
|
return typeofObj
|
||||||
|
}
|
||||||
|
if (obj === null) {
|
||||||
|
return 'null'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(obj) && (!ste || !(Symbol.toStringTag in obj))) {
|
||||||
|
return 'Array'
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringTag = ste && obj[Symbol.toStringTag]
|
||||||
|
if (typeof stringTag === 'string') {
|
||||||
|
return stringTag
|
||||||
|
}
|
||||||
|
|
||||||
|
const objPrototype = Object.getPrototypeOf(obj)
|
||||||
|
|
||||||
|
if (objPrototype === RegExp.prototype) {
|
||||||
|
return 'RegExp'
|
||||||
|
}
|
||||||
|
if (objPrototype === Date.prototype) {
|
||||||
|
return 'Date'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objPrototype === null) {
|
||||||
|
return 'Object'
|
||||||
|
}
|
||||||
|
return {}.toString.call(obj).slice(8, -1)
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Status, OK, unknownError } from './status'
|
import { Status, OK, unknownError, Severity } from './status'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -68,7 +68,9 @@ async function broadcastEvent (event: string, data: any): Promise<void> {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function setPlatformStatus (status: Status): Promise<void> {
|
export async function setPlatformStatus (status: Status): Promise<void> {
|
||||||
// console.log(await translate(status.code, status.params))
|
if (status.severity === Severity.ERROR) {
|
||||||
|
console.trace('Platform Error Status', status)
|
||||||
|
}
|
||||||
return await broadcastEvent(PlatformEvent, status)
|
return await broadcastEvent(PlatformEvent, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,11 +12,26 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
<script lang="ts" context="module">
|
||||||
|
const providers = new Map<string, AvatarProvider | null>()
|
||||||
|
|
||||||
|
async function getProvider (client: Client, providerId: Ref<AvatarProvider>): Promise<AvatarProvider | undefined> {
|
||||||
|
const p = providers.get(providerId)
|
||||||
|
if (p !== undefined) {
|
||||||
|
return p ?? undefined
|
||||||
|
}
|
||||||
|
const res = await getClient().findOne(contact.class.AvatarProvider, { _id: providerId })
|
||||||
|
providers.set(providerId, res ?? null)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact, { AvatarType, AvatarProvider } from '@hcengineering/contact'
|
import contact, { AvatarProvider, AvatarType } from '@hcengineering/contact'
|
||||||
|
import { Client, Ref } from '@hcengineering/core'
|
||||||
import { Asset, getResource } from '@hcengineering/platform'
|
import { Asset, getResource } from '@hcengineering/platform'
|
||||||
import { AnySvelteComponent, Icon, IconSize } from '@hcengineering/ui'
|
import { AnySvelteComponent, Icon, IconSize } from '@hcengineering/ui'
|
||||||
import { getBlobURL, getAvatarProviderId, getClient } from '../utils'
|
import { getAvatarProviderId, getBlobURL, getClient } from '../utils'
|
||||||
import AvatarIcon from './icons/Avatar.svelte'
|
import AvatarIcon from './icons/Avatar.svelte'
|
||||||
|
|
||||||
export let avatar: string | null | undefined = undefined
|
export let avatar: string | null | undefined = undefined
|
||||||
@ -35,8 +50,7 @@
|
|||||||
})
|
})
|
||||||
} else if (avatar) {
|
} else if (avatar) {
|
||||||
const avatarProviderId = getAvatarProviderId(avatar)
|
const avatarProviderId = getAvatarProviderId(avatar)
|
||||||
avatarProvider =
|
avatarProvider = avatarProviderId && (await getProvider(getClient(), avatarProviderId))
|
||||||
avatarProviderId && (await getClient().findOne(contact.class.AvatarProvider, { _id: avatarProviderId }))
|
|
||||||
|
|
||||||
if (!avatarProvider || avatarProvider.type === AvatarType.COLOR) {
|
if (!avatarProvider || avatarProvider.type === AvatarType.COLOR) {
|
||||||
url = undefined
|
url = undefined
|
||||||
|
@ -19,11 +19,19 @@
|
|||||||
export let label: IntlString
|
export let label: IntlString
|
||||||
export let params: Record<string, any> = {}
|
export let params: Record<string, any> = {}
|
||||||
|
|
||||||
$: translation = translate(label, params)
|
let _value: string | undefined = undefined
|
||||||
|
|
||||||
|
$: if (label !== undefined) {
|
||||||
|
translate(label, params ?? {}).then((r) => {
|
||||||
|
_value = r
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
_value = label
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await translation}
|
{#if _value}
|
||||||
|
{_value}
|
||||||
|
{:else}
|
||||||
{label}
|
{label}
|
||||||
{:then text}
|
{/if}
|
||||||
{text}
|
|
||||||
{/await}
|
|
||||||
|
@ -23,7 +23,6 @@ export default mergeIds(attachmentId, attachment, {
|
|||||||
string: {
|
string: {
|
||||||
NoAttachments: '' as IntlString,
|
NoAttachments: '' as IntlString,
|
||||||
UploadDropFilesHere: '' as IntlString,
|
UploadDropFilesHere: '' as IntlString,
|
||||||
Attachments: '' as IntlString,
|
|
||||||
Photos: '' as IntlString,
|
Photos: '' as IntlString,
|
||||||
FileBrowser: '' as IntlString,
|
FileBrowser: '' as IntlString,
|
||||||
FileBrowserFileCounter: '' as IntlString,
|
FileBrowserFileCounter: '' as IntlString,
|
||||||
|
@ -86,6 +86,7 @@ export default plugin(attachmentId, {
|
|||||||
FileBrowserTypeFilterAudio: '' as IntlString,
|
FileBrowserTypeFilterAudio: '' as IntlString,
|
||||||
FileBrowserTypeFilterVideos: '' as IntlString,
|
FileBrowserTypeFilterVideos: '' as IntlString,
|
||||||
FileBrowserTypeFilterPDFs: '' as IntlString,
|
FileBrowserTypeFilterPDFs: '' as IntlString,
|
||||||
DeleteFile: '' as IntlString
|
DeleteFile: '' as IntlString,
|
||||||
|
Attachments: '' as IntlString
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
TabList
|
TabList
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { FilterButton, ViewletSettingButton } from '@hcengineering/view-resources'
|
import { FilterButton, getViewOptions, ViewletSettingButton } from '@hcengineering/view-resources'
|
||||||
import calendar from '../plugin'
|
import calendar from '../plugin'
|
||||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||||
|
|
||||||
@ -101,6 +101,8 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
$: twoRows = $deviceInfo.twoRows
|
$: twoRows = $deviceInfo.twoRows
|
||||||
|
|
||||||
|
$: viewOptions = getViewOptions(selectedViewlet)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
||||||
@ -133,7 +135,7 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<ViewletSettingButton viewlet={selectedViewlet} />
|
<ViewletSettingButton bind:viewOptions viewlet={selectedViewlet} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -148,6 +150,7 @@
|
|||||||
space,
|
space,
|
||||||
options: selectedViewlet.options,
|
options: selectedViewlet.options,
|
||||||
config: preference?.config ?? selectedViewlet.config,
|
config: preference?.config ?? selectedViewlet.config,
|
||||||
|
viewOptions,
|
||||||
viewlet: selectedViewlet,
|
viewlet: selectedViewlet,
|
||||||
query: resultQuery,
|
query: resultQuery,
|
||||||
search,
|
search,
|
||||||
|
@ -18,7 +18,13 @@
|
|||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@hcengineering/ui'
|
import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { ActionContext, FilterButton, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
|
import {
|
||||||
|
ActionContext,
|
||||||
|
FilterButton,
|
||||||
|
getViewOptions,
|
||||||
|
TableBrowser,
|
||||||
|
ViewletSettingButton
|
||||||
|
} from '@hcengineering/view-resources'
|
||||||
import contact from '../plugin'
|
import contact from '../plugin'
|
||||||
import CreateContact from './CreateContact.svelte'
|
import CreateContact from './CreateContact.svelte'
|
||||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||||
@ -64,6 +70,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: twoRows = $deviceInfo.twoRows
|
$: twoRows = $deviceInfo.twoRows
|
||||||
|
|
||||||
|
$: viewOptions = getViewOptions(viewlet)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionContext
|
<ActionContext
|
||||||
@ -95,7 +103,7 @@
|
|||||||
size={'small'}
|
size={'small'}
|
||||||
on:click={(ev) => showCreateDialog(ev)}
|
on:click={(ev) => showCreateDialog(ev)}
|
||||||
/>
|
/>
|
||||||
<ViewletSettingButton {viewlet} />
|
<ViewletSettingButton bind:viewOptions {viewlet} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import { createQuery, getClient, UsersPopup, IconMembersOutline } from '@hcengineering/presentation'
|
import { createQuery, getClient, UsersPopup, IconMembersOutline } from '@hcengineering/presentation'
|
||||||
import { Button, IconAdd, Label, showPopup, Icon } from '@hcengineering/ui'
|
import { Button, IconAdd, Label, showPopup, Icon } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { Table, ViewletSettingButton } from '@hcengineering/view-resources'
|
import { getViewOptions, Table, ViewletSettingButton } from '@hcengineering/view-resources'
|
||||||
import contact from '../plugin'
|
import contact from '../plugin'
|
||||||
|
|
||||||
export let objectId: Ref<Doc>
|
export let objectId: Ref<Doc>
|
||||||
@ -90,6 +90,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
$: viewOptions = getViewOptions(descr)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="antiSection">
|
<div class="antiSection">
|
||||||
@ -101,7 +102,7 @@
|
|||||||
<Label label={contact.string.Members} />
|
<Label label={contact.string.Members} />
|
||||||
</span>
|
</span>
|
||||||
<div class="buttons-group xsmall-gap">
|
<div class="buttons-group xsmall-gap">
|
||||||
<ViewletSettingButton viewlet={descr} />
|
<ViewletSettingButton bind:viewOptions viewlet={descr} />
|
||||||
<Button id={contact.string.AddMember} icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={createApp} />
|
<Button id={contact.string.AddMember} icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={createApp} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -118,6 +119,7 @@
|
|||||||
<span class="dark-color">
|
<span class="dark-color">
|
||||||
<Label label={contact.string.NoMembers} />
|
<Label label={contact.string.NoMembers} />
|
||||||
</span>
|
</span>
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<span class="over-underline content-accent-color" on:click={createApp}>
|
<span class="over-underline content-accent-color" on:click={createApp}>
|
||||||
<Label label={contact.string.AddMember} />
|
<Label label={contact.string.AddMember} />
|
||||||
</span>
|
</span>
|
||||||
|
@ -20,7 +20,13 @@
|
|||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { deviceOptionsStore as deviceInfo, Icon, Label, Loading, SearchEdit } from '@hcengineering/ui'
|
import { deviceOptionsStore as deviceInfo, Icon, Label, Loading, SearchEdit } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { ActionContext, FilterButton, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
|
import {
|
||||||
|
ActionContext,
|
||||||
|
FilterButton,
|
||||||
|
getViewOptions,
|
||||||
|
TableBrowser,
|
||||||
|
ViewletSettingButton
|
||||||
|
} from '@hcengineering/view-resources'
|
||||||
import document from '../plugin'
|
import document from '../plugin'
|
||||||
|
|
||||||
export let query: DocumentQuery<Document> = {}
|
export let query: DocumentQuery<Document> = {}
|
||||||
@ -63,6 +69,8 @@
|
|||||||
|
|
||||||
let twoRows: boolean
|
let twoRows: boolean
|
||||||
$: twoRows = $deviceInfo.docWidth <= 680
|
$: twoRows = $deviceInfo.docWidth <= 680
|
||||||
|
|
||||||
|
$: viewOptions = getViewOptions(viewlet)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionContext
|
<ActionContext
|
||||||
@ -87,7 +95,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="ac-header-full" class:secondRow={twoRows}>
|
<div class="ac-header-full" class:secondRow={twoRows}>
|
||||||
<ViewletSettingButton {viewlet} />
|
<ViewletSettingButton bind:viewOptions {viewlet} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
import { createQuery, getClient, UsersPopup } from '@hcengineering/presentation'
|
import { createQuery, getClient, UsersPopup } from '@hcengineering/presentation'
|
||||||
import { Button, eventToHTMLElement, IconAdd, Label, Scroller, showPopup } from '@hcengineering/ui'
|
import { Button, eventToHTMLElement, IconAdd, Label, Scroller, showPopup } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { Table, ViewletSettingButton } from '@hcengineering/view-resources'
|
import { getViewOptions, Table, ViewletSettingButton } from '@hcengineering/view-resources'
|
||||||
import hr from '../plugin'
|
import hr from '../plugin'
|
||||||
import { addMember } from '../utils'
|
import { addMember } from '../utils'
|
||||||
|
|
||||||
@ -91,6 +91,8 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: viewOptions = getViewOptions(descr)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="antiSection">
|
<div class="antiSection">
|
||||||
@ -99,7 +101,7 @@
|
|||||||
<Label label={hr.string.Members} />
|
<Label label={hr.string.Members} />
|
||||||
</span>
|
</span>
|
||||||
<div class="buttons-group xsmall-gap">
|
<div class="buttons-group xsmall-gap">
|
||||||
<ViewletSettingButton viewlet={descr} />
|
<ViewletSettingButton bind:viewOptions viewlet={descr} />
|
||||||
<Button id={hr.string.AddEmployee} icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={add} />
|
<Button id={hr.string.AddEmployee} icon={IconAdd} kind={'transparent'} shape={'circle'} on:click={add} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Button, Label, Loading, Scroller, tableSP } from '@hcengineering/ui'
|
import { Button, Label, Loading, Scroller, tableSP } from '@hcengineering/ui'
|
||||||
import view, { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { Table, ViewletSettingButton } from '@hcengineering/view-resources'
|
import { getViewOptions, Table, ViewletSettingButton } from '@hcengineering/view-resources'
|
||||||
import hr from '../../plugin'
|
import hr from '../../plugin'
|
||||||
import {
|
import {
|
||||||
EmployeeReports,
|
EmployeeReports,
|
||||||
@ -215,6 +215,8 @@
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: viewOptions = getViewOptions(descr)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if departmentStaff.length}
|
{#if departmentStaff.length}
|
||||||
@ -226,7 +228,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="flex-row-center flex-reverse">
|
<div class="flex-row-center flex-reverse">
|
||||||
<div class="ml-1">
|
<div class="ml-1">
|
||||||
<ViewletSettingButton viewlet={descr} />
|
<ViewletSettingButton bind:viewOptions viewlet={descr} />
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
label={getEmbeddedLabel('Export')}
|
label={getEmbeddedLabel('Export')}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
import { Vacancy } from '@hcengineering/recruit'
|
import { Vacancy } from '@hcengineering/recruit'
|
||||||
import { FullDescriptionBox } from '@hcengineering/text-editor'
|
import { FullDescriptionBox } from '@hcengineering/text-editor'
|
||||||
import tracker from '@hcengineering/tracker'
|
import tracker from '@hcengineering/tracker'
|
||||||
import { Button, Component, EditBox, Grid, Icon, IconAdd, IconMoreH, Label, showPopup } from '@hcengineering/ui'
|
import { Button, Component, EditBox, Grid, IconMoreH, showPopup } from '@hcengineering/ui'
|
||||||
import { ClassAttributeBar, ContextMenu } from '@hcengineering/view-resources'
|
import { ClassAttributeBar, ContextMenu } from '@hcengineering/view-resources'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import recruit from '../plugin'
|
import recruit from '../plugin'
|
||||||
@ -131,33 +131,7 @@
|
|||||||
/>
|
/>
|
||||||
<!-- <MembersBox label={recruit.string.Members} space={object} /> -->
|
<!-- <MembersBox label={recruit.string.Members} space={object} /> -->
|
||||||
|
|
||||||
<div class="antiSection">
|
<Component is={tracker.component.RelatedIssuesSection} props={{ object, label: recruit.string.RelatedIssues }} />
|
||||||
<div class="antiSection-header">
|
</Grid>
|
||||||
<div class="antiSection-header__icon">
|
|
||||||
<Icon icon={tracker.icon.Issue} size={'small'} />
|
|
||||||
</div>
|
|
||||||
<span class="antiSection-header__title">
|
|
||||||
<Label label={recruit.string.RelatedIssues} />
|
|
||||||
</span>
|
|
||||||
<div class="buttons-group small-gap">
|
|
||||||
<Button
|
|
||||||
id="add-sub-issue"
|
|
||||||
width="min-content"
|
|
||||||
icon={IconAdd}
|
|
||||||
label={undefined}
|
|
||||||
labelParams={{ subIssues: 0 }}
|
|
||||||
kind={'transparent'}
|
|
||||||
size={'small'}
|
|
||||||
on:click={() => {
|
|
||||||
showPopup(tracker.component.CreateIssue, { relatedTo: object, space: object.space }, 'top')
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-row">
|
|
||||||
<Component is={tracker.component.RelatedIssues} props={{ object }} />
|
|
||||||
</div>
|
|
||||||
</div></Grid
|
|
||||||
>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import { Vacancy } from '@hcengineering/recruit'
|
import { Vacancy } from '@hcengineering/recruit'
|
||||||
import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@hcengineering/ui'
|
import { Button, Icon, IconAdd, Label, Loading, SearchEdit, showPopup } from '@hcengineering/ui'
|
||||||
import view, { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
|
import view, { BuildModelKey, Viewlet, ViewletPreference } from '@hcengineering/view'
|
||||||
import { FilterButton, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
|
import { FilterButton, getViewOptions, TableBrowser, ViewletSettingButton } from '@hcengineering/view-resources'
|
||||||
import recruit from '../plugin'
|
import recruit from '../plugin'
|
||||||
import CreateVacancy from './CreateVacancy.svelte'
|
import CreateVacancy from './CreateVacancy.svelte'
|
||||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||||
@ -132,6 +132,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: twoRows = $deviceInfo.twoRows
|
$: twoRows = $deviceInfo.twoRows
|
||||||
|
|
||||||
|
$: viewOptions = getViewOptions(descr)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
||||||
@ -156,7 +158,7 @@
|
|||||||
kind={'primary'}
|
kind={'primary'}
|
||||||
on:click={showCreateDialog}
|
on:click={showCreateDialog}
|
||||||
/>
|
/>
|
||||||
<ViewletSettingButton viewlet={descr} />
|
<ViewletSettingButton bind:viewOptions viewlet={descr} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"build:docs": "api-extractor run --local",
|
"build:docs": "api-extractor run --local",
|
||||||
"lint": "svelte-check && eslint",
|
"lint": "svelte-check && eslint",
|
||||||
"lint:fix": "eslint --fix src",
|
"lint:fix": "eslint --fix src",
|
||||||
|
"svelte-check": "svelte-check",
|
||||||
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src"
|
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -91,6 +91,7 @@
|
|||||||
<div class="ap-scroll">
|
<div class="ap-scroll">
|
||||||
<div class="ap-box">
|
<div class="ap-box">
|
||||||
{#await getItems() then items}
|
{#await getItems() then items}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
class="ap-menuItem flex-row-center"
|
class="ap-menuItem flex-row-center"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
refDocument: Doc,
|
refDocument: Doc,
|
||||||
type: keyof typeof relations,
|
type: keyof typeof relations,
|
||||||
operation: '$push' | '$pull',
|
operation: '$push' | '$pull',
|
||||||
placeholder: IntlString
|
_: IntlString
|
||||||
) {
|
) {
|
||||||
const prop = type === 'isBlocking' ? 'blockedBy' : type
|
const prop = type === 'isBlocking' ? 'blockedBy' : type
|
||||||
if (type !== 'isBlocking') {
|
if (type !== 'isBlocking') {
|
||||||
@ -69,7 +69,7 @@
|
|||||||
await update(value, type, docs, label)
|
await update(value, type, docs, label)
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeAddAction = (type: keyof typeof relations, placeholder: IntlString) => async (props: any, evt: Event) => {
|
const makeAddAction = (type: keyof typeof relations, placeholder: IntlString) => async () => {
|
||||||
closePopup('popup')
|
closePopup('popup')
|
||||||
showPopup(
|
showPopup(
|
||||||
ObjectSearchPopup,
|
ObjectSearchPopup,
|
||||||
@ -81,7 +81,7 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
async function removeRelation (evt: MouseEvent) {
|
async function removeRelation () {
|
||||||
closePopup('popup')
|
closePopup('popup')
|
||||||
showPopup(
|
showPopup(
|
||||||
ObjectSearchPopup,
|
ObjectSearchPopup,
|
||||||
|
@ -44,14 +44,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: tooltipValue = new Date(value).toLocaleString('default', {
|
|
||||||
minute: '2-digit',
|
|
||||||
hour: 'numeric',
|
|
||||||
day: '2-digit',
|
|
||||||
month: 'short',
|
|
||||||
year: 'numeric'
|
|
||||||
})
|
|
||||||
|
|
||||||
$: formatTime(value)
|
$: formatTime(value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition'
|
|
||||||
import {
|
|
||||||
NotificationSeverity,
|
|
||||||
Notification,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
IconClose,
|
|
||||||
IconInfo,
|
|
||||||
IconCheckCircle,
|
|
||||||
Label,
|
|
||||||
showPanel
|
|
||||||
} from '@hcengineering/ui'
|
|
||||||
import { copyTextToClipboard, createQuery } from '@hcengineering/presentation'
|
import { copyTextToClipboard, createQuery } from '@hcengineering/presentation'
|
||||||
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
||||||
|
import {
|
||||||
|
AnySvelteComponent,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
IconCheckCircle,
|
||||||
|
IconClose,
|
||||||
|
IconInfo,
|
||||||
|
Notification,
|
||||||
|
NotificationSeverity,
|
||||||
|
showPanel
|
||||||
|
} from '@hcengineering/ui'
|
||||||
|
import { fade } from 'svelte/transition'
|
||||||
|
|
||||||
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
|
||||||
import IssuePresenter from './IssuePresenter.svelte'
|
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
import IssuePresenter from './IssuePresenter.svelte'
|
||||||
|
import IssueStatusIcon from './IssueStatusIcon.svelte'
|
||||||
|
|
||||||
export let notification: Notification
|
export let notification: Notification
|
||||||
export let onRemove: () => void
|
export let onRemove: () => void
|
||||||
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
$: issueQuery.query(
|
$: issueQuery.query(
|
||||||
tracker.class.Issue,
|
tracker.class.Issue,
|
||||||
{ _id: params.issueId },
|
{ _id: params?.issueId },
|
||||||
(res) => {
|
(res) => {
|
||||||
issue = res[0]
|
issue = res[0]
|
||||||
},
|
},
|
||||||
@ -49,7 +49,7 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getIcon = () => {
|
const getIcon = (): AnySvelteComponent | undefined => {
|
||||||
switch (severity) {
|
switch (severity) {
|
||||||
case NotificationSeverity.Success:
|
case NotificationSeverity.Success:
|
||||||
return IconCheckCircle
|
return IconCheckCircle
|
||||||
@ -84,14 +84,17 @@
|
|||||||
copyTextToClipboard(params?.issueUrl)
|
copyTextToClipboard(params?.issueUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$: icon = getIcon()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root" in:fade out:fade>
|
<div class="root" in:fade out:fade>
|
||||||
<Icon icon={getIcon()} size="medium" fill={getIconColor()} />
|
{#if icon}
|
||||||
|
<Icon {icon} size="medium" fill={getIconColor()} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Label label={title} />
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="issue">
|
<div class="issue">
|
||||||
@ -105,7 +108,7 @@
|
|||||||
{subTitle}
|
{subTitle}
|
||||||
</div>
|
</div>
|
||||||
<div class="postfix">
|
<div class="postfix">
|
||||||
{params.subTitlePostfix}
|
{params?.subTitlePostfix}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WithLookup } from '@hcengineering/core'
|
import { Ref, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import type { Issue, Team } from '@hcengineering/tracker'
|
import type { Issue, Team } from '@hcengineering/tracker'
|
||||||
import { showPanel } from '@hcengineering/ui'
|
import { showPanel } from '@hcengineering/ui'
|
||||||
@ -23,6 +23,9 @@
|
|||||||
export let disableClick = false
|
export let disableClick = false
|
||||||
export let onClick: (() => void) | undefined = undefined
|
export let onClick: (() => void) | undefined = undefined
|
||||||
|
|
||||||
|
// Extra properties
|
||||||
|
export let teams: Map<Ref<Team>, Team> | undefined = undefined
|
||||||
|
|
||||||
function handleIssueEditorOpened () {
|
function handleIssueEditorOpened () {
|
||||||
if (disableClick) {
|
if (disableClick) {
|
||||||
return
|
return
|
||||||
@ -38,16 +41,21 @@
|
|||||||
const spaceQuery = createQuery()
|
const spaceQuery = createQuery()
|
||||||
let currentTeam: Team | undefined = value?.$lookup?.space
|
let currentTeam: Team | undefined = value?.$lookup?.space
|
||||||
|
|
||||||
$: if (value && value?.$lookup?.space === undefined) {
|
$: if (teams === undefined) {
|
||||||
spaceQuery.query(tracker.class.Team, { _id: value.space }, (res) => ([currentTeam] = res))
|
if (value && value?.$lookup?.space === undefined) {
|
||||||
|
spaceQuery.query(tracker.class.Team, { _id: value.space }, (res) => ([currentTeam] = res))
|
||||||
|
} else {
|
||||||
|
spaceQuery.unsubscribe()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
spaceQuery.unsubscribe()
|
currentTeam = teams.get(value.space)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: title = currentTeam ? `${currentTeam.identifier}-${value?.number}` : `${value?.number}`
|
$: title = currentTeam ? `${currentTeam.identifier}-${value?.number}` : `${value?.number}`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<span
|
<span
|
||||||
class="issuePresenterRoot"
|
class="issuePresenterRoot"
|
||||||
class:noPointer={disableClick}
|
class:noPointer={disableClick}
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
async function updateStatus (
|
async function updateStatus (
|
||||||
txes: Tx[],
|
txes: Tx[],
|
||||||
statuses: Map<Ref<IssueStatus>, WithLookup<IssueStatus>>,
|
statuses: Map<Ref<IssueStatus>, WithLookup<IssueStatus>>,
|
||||||
now: number
|
_: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const result: WithTime[] = []
|
const result: WithTime[] = []
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
export let size: IconSize
|
export let size: IconSize
|
||||||
export let fill: string | undefined = undefined
|
export let fill: string | undefined = undefined
|
||||||
|
|
||||||
|
export let issueStatuses: IssueStatus[] | undefined = undefined
|
||||||
|
|
||||||
const dynamicFillCategories = [tracker.issueStatusCategory.Started]
|
const dynamicFillCategories = [tracker.issueStatusCategory.Started]
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
@ -36,12 +38,19 @@
|
|||||||
} = { index: undefined, count: undefined }
|
} = { index: undefined, count: undefined }
|
||||||
|
|
||||||
const categoriesQuery = createQuery()
|
const categoriesQuery = createQuery()
|
||||||
categoriesQuery.query(
|
|
||||||
tracker.class.IssueStatus,
|
$: if (issueStatuses === undefined) {
|
||||||
{ category: tracker.issueStatusCategory.Started },
|
categoriesQuery.query(
|
||||||
(res) => (statuses = res),
|
tracker.class.IssueStatus,
|
||||||
{ sort: { rank: SortingOrder.Ascending } }
|
{ category: tracker.issueStatusCategory.Started },
|
||||||
)
|
(res) => (statuses = res),
|
||||||
|
{ sort: { rank: SortingOrder.Ascending } }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const _s = [...issueStatuses.filter((it) => it.category === tracker.issueStatusCategory.Started)]
|
||||||
|
_s.sort((a, b) => a.rank.localeCompare(b.rank))
|
||||||
|
categoriesQuery.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
async function updateCategory (status: WithLookup<IssueStatus>, statuses: IssueStatus[]) {
|
async function updateCategory (status: WithLookup<IssueStatus>, statuses: IssueStatus[]) {
|
||||||
if (status.$lookup?.category) {
|
if (status.$lookup?.category) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import { Issue } from '@hcengineering/tracker'
|
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
|
||||||
import { Component } from '@hcengineering/ui'
|
import { Component } from '@hcengineering/ui'
|
||||||
import { Viewlet } from '@hcengineering/view'
|
import { Viewlet, ViewOptions } from '@hcengineering/view'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import CreateIssue from '../CreateIssue.svelte'
|
import CreateIssue from '../CreateIssue.svelte'
|
||||||
|
|
||||||
@ -10,6 +10,11 @@
|
|||||||
export let query: DocumentQuery<Issue> = {}
|
export let query: DocumentQuery<Issue> = {}
|
||||||
export let space: Ref<Space> | undefined
|
export let space: Ref<Space> | undefined
|
||||||
|
|
||||||
|
// Extra properties
|
||||||
|
export let teams: Map<Ref<Team>, Team> | undefined
|
||||||
|
export let issueStatuses: Map<Ref<Team>, WithLookup<IssueStatus>[]>
|
||||||
|
export let viewOptions: ViewOptions
|
||||||
|
|
||||||
const createItemDialog = CreateIssue
|
const createItemDialog = CreateIssue
|
||||||
const createItemLabel = tracker.string.AddIssueTooltip
|
const createItemLabel = tracker.string.AddIssueTooltip
|
||||||
</script>
|
</script>
|
||||||
@ -24,9 +29,11 @@
|
|||||||
createItemDialog,
|
createItemDialog,
|
||||||
createItemLabel,
|
createItemLabel,
|
||||||
viewlet,
|
viewlet,
|
||||||
viewOptions: viewlet.viewOptions?.other,
|
viewOptions,
|
||||||
|
viewOptionsConfig: viewlet.viewOptions?.other,
|
||||||
space,
|
space,
|
||||||
query
|
query,
|
||||||
|
props: { teams, issueStatuses }
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
$: groupedByProject = getGroupedIssues('project', issues)
|
$: groupedByProject = getGroupedIssues('project', issues)
|
||||||
$: groupedBySprint = getGroupedIssues('sprint', issues)
|
$: groupedBySprint = getGroupedIssues('sprint', issues)
|
||||||
|
|
||||||
const handleStatusFilterMenuSectionOpened = (event: MouseEvent | KeyboardEvent) => {
|
const handleStatusFilterMenuSectionOpened = () => {
|
||||||
const statusGroups: { [key: string]: number } = {}
|
const statusGroups: { [key: string]: number } = {}
|
||||||
|
|
||||||
for (const status of defaultStatuses) {
|
for (const status of defaultStatuses) {
|
||||||
@ -66,7 +66,7 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePriorityFilterMenuSectionOpened = (event: MouseEvent | KeyboardEvent) => {
|
const handlePriorityFilterMenuSectionOpened = () => {
|
||||||
const priorityGroups: { [key: string]: number } = {}
|
const priorityGroups: { [key: string]: number } = {}
|
||||||
|
|
||||||
for (const priority of defaultPriorities) {
|
for (const priority of defaultPriorities) {
|
||||||
@ -86,7 +86,7 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleProjectFilterMenuSectionOpened = (event: MouseEvent | KeyboardEvent) => {
|
const handleProjectFilterMenuSectionOpened = () => {
|
||||||
const projectGroups: { [key: string]: number } = {}
|
const projectGroups: { [key: string]: number } = {}
|
||||||
|
|
||||||
for (const [project, value] of Object.entries(groupedByProject)) {
|
for (const [project, value] of Object.entries(groupedByProject)) {
|
||||||
@ -105,7 +105,7 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSprintFilterMenuSectionOpened = (event: MouseEvent | KeyboardEvent) => {
|
const handleSprintFilterMenuSectionOpened = () => {
|
||||||
const sprintGroups: { [key: string]: number } = {}
|
const sprintGroups: { [key: string]: number } = {}
|
||||||
|
|
||||||
for (const [project, value] of Object.entries(groupedBySprint)) {
|
for (const [project, value] of Object.entries(groupedBySprint)) {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
import { DocumentQuery, Ref, SortingOrder, Space, WithLookup } from '@hcengineering/core'
|
||||||
import { IntlString, translate } from '@hcengineering/platform'
|
import { IntlString, translate } from '@hcengineering/platform'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Issue } from '@hcengineering/tracker'
|
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
|
||||||
import { Button, IconDetails, IconDetailsFilled } from '@hcengineering/ui'
|
import { Button, IconDetails, IconDetailsFilled } from '@hcengineering/ui'
|
||||||
import view, { Viewlet } from '@hcengineering/view'
|
import view, { Viewlet } from '@hcengineering/view'
|
||||||
import { FilterBar, getActiveViewletId } from '@hcengineering/view-resources'
|
import { FilterBar, getActiveViewletId, getViewOptions } from '@hcengineering/view-resources'
|
||||||
import ViewletSettingButton from '@hcengineering/view-resources/src/components/ViewletSettingButton.svelte'
|
import ViewletSettingButton from '@hcengineering/view-resources/src/components/ViewletSettingButton.svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import IssuesContent from './IssuesContent.svelte'
|
import IssuesContent from './IssuesContent.svelte'
|
||||||
@ -36,7 +36,7 @@
|
|||||||
async function update (): Promise<void> {
|
async function update (): Promise<void> {
|
||||||
viewlets = await client.findAll(
|
viewlets = await client.findAll(
|
||||||
view.class.Viewlet,
|
view.class.Viewlet,
|
||||||
{ attachTo: tracker.class.Issue },
|
{ attachTo: tracker.class.Issue, variant: { $ne: 'subissue' } },
|
||||||
{
|
{
|
||||||
lookup: {
|
lookup: {
|
||||||
descriptor: view.class.ViewletDescriptor
|
descriptor: view.class.ViewletDescriptor
|
||||||
@ -63,6 +63,41 @@
|
|||||||
let docSize: boolean = false
|
let docSize: boolean = false
|
||||||
$: if (docWidth <= 900 && !docSize) docSize = true
|
$: if (docWidth <= 900 && !docSize) docSize = true
|
||||||
$: if (docWidth > 900 && docSize) docSize = false
|
$: if (docWidth > 900 && docSize) docSize = false
|
||||||
|
|
||||||
|
const teamQuery = createQuery()
|
||||||
|
|
||||||
|
let _teams: Map<Ref<Team>, Team> | undefined = undefined
|
||||||
|
let _result: any
|
||||||
|
$: teamQuery.query(tracker.class.Team, {}, (result) => {
|
||||||
|
_result = JSON.stringify(result, undefined, 2)
|
||||||
|
console.log('#RESULT 124', _result)
|
||||||
|
const t = new Map<Ref<Team>, Team>()
|
||||||
|
for (const r of result) {
|
||||||
|
t.set(r._id, r)
|
||||||
|
}
|
||||||
|
_teams = t
|
||||||
|
})
|
||||||
|
|
||||||
|
let issueStatuses: Map<Ref<Team>, WithLookup<IssueStatus>[]>
|
||||||
|
|
||||||
|
const statusesQuery = createQuery()
|
||||||
|
statusesQuery.query(
|
||||||
|
tracker.class.IssueStatus,
|
||||||
|
{},
|
||||||
|
(statuses) => {
|
||||||
|
const st = new Map<Ref<Team>, WithLookup<IssueStatus>[]>()
|
||||||
|
for (const s of statuses) {
|
||||||
|
const id = s.attachedTo as Ref<Team>
|
||||||
|
st.set(id, [...(st.get(id) ?? []), s])
|
||||||
|
}
|
||||||
|
issueStatuses = st
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup: { category: tracker.class.IssueStatusCategory },
|
||||||
|
sort: { rank: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
$: viewOptions = getViewOptions(viewlet)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IssuesHeader {viewlets} {label} bind:viewlet bind:search showLabelSelector={$$slots.label_selector}>
|
<IssuesHeader {viewlets} {label} bind:viewlet bind:search showLabelSelector={$$slots.label_selector}>
|
||||||
@ -71,7 +106,7 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="extra">
|
<svelte:fragment slot="extra">
|
||||||
{#if viewlet}
|
{#if viewlet}
|
||||||
<ViewletSettingButton {viewlet} />
|
<ViewletSettingButton bind:viewOptions {viewlet} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if asideFloat && $$slots.aside}
|
{#if asideFloat && $$slots.aside}
|
||||||
<div class="buttons-divider" />
|
<div class="buttons-divider" />
|
||||||
@ -90,8 +125,8 @@
|
|||||||
<slot name="afterHeader" />
|
<slot name="afterHeader" />
|
||||||
<FilterBar _class={tracker.class.Issue} query={searchQuery} on:change={(e) => (resultQuery = e.detail)} />
|
<FilterBar _class={tracker.class.Issue} query={searchQuery} on:change={(e) => (resultQuery = e.detail)} />
|
||||||
<div class="flex w-full h-full clear-mins">
|
<div class="flex w-full h-full clear-mins">
|
||||||
{#if viewlet}
|
{#if viewlet && _teams && issueStatuses}
|
||||||
<IssuesContent {viewlet} query={resultQuery} {space} />
|
<IssuesContent {viewlet} query={resultQuery} {space} teams={_teams} {issueStatuses} {viewOptions} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if $$slots.aside !== undefined && asideShown}
|
{#if $$slots.aside !== undefined && asideShown}
|
||||||
<div class="popupPanel-body__aside flex" class:float={asideFloat} class:shown={asideShown}>
|
<div class="popupPanel-body__aside flex" class:float={asideFloat} class:shown={asideShown}>
|
||||||
|
@ -37,8 +37,7 @@
|
|||||||
ListSelectionProvider,
|
ListSelectionProvider,
|
||||||
noCategory,
|
noCategory,
|
||||||
SelectDirection,
|
SelectDirection,
|
||||||
selectionStore,
|
selectionStore
|
||||||
viewOptionsStore
|
|
||||||
} from '@hcengineering/view-resources'
|
} from '@hcengineering/view-resources'
|
||||||
import ActionContext from '@hcengineering/view-resources/src/components/ActionContext.svelte'
|
import ActionContext from '@hcengineering/view-resources/src/components/ActionContext.svelte'
|
||||||
import Menu from '@hcengineering/view-resources/src/components/Menu.svelte'
|
import Menu from '@hcengineering/view-resources/src/components/Menu.svelte'
|
||||||
@ -59,11 +58,12 @@
|
|||||||
export let space: Ref<Team> | undefined = undefined
|
export let space: Ref<Team> | undefined = undefined
|
||||||
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
||||||
export let query: DocumentQuery<Issue> = {}
|
export let query: DocumentQuery<Issue> = {}
|
||||||
export let viewOptions: ViewOptionModel[] | undefined
|
export let viewOptionsConfig: ViewOptionModel[] | undefined
|
||||||
|
export let viewOptions: ViewOptions
|
||||||
|
|
||||||
$: currentSpace = space || tracker.team.DefaultTeam
|
$: currentSpace = space || tracker.team.DefaultTeam
|
||||||
$: groupBy = ($viewOptionsStore.groupBy ?? noCategory) as IssuesGrouping
|
$: groupBy = (viewOptions.groupBy ?? noCategory) as IssuesGrouping
|
||||||
$: orderBy = $viewOptionsStore.orderBy
|
$: orderBy = viewOptions.orderBy
|
||||||
$: sort = { [orderBy[0]]: orderBy[1] }
|
$: sort = { [orderBy[0]]: orderBy[1] }
|
||||||
$: dontUpdateRank = orderBy[0] !== IssuesOrdering.Manual
|
$: dontUpdateRank = orderBy[0] !== IssuesOrdering.Manual
|
||||||
|
|
||||||
@ -76,7 +76,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
let resultQuery: DocumentQuery<any> = query
|
let resultQuery: DocumentQuery<any> = query
|
||||||
$: getResultQuery(query, viewOptions, $viewOptionsStore).then((p) => (resultQuery = p))
|
$: getResultQuery(query, viewOptionsConfig, viewOptions).then((p) => (resultQuery = p))
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
@ -188,6 +188,7 @@
|
|||||||
mode: 'browser'
|
mode: 'browser'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<Kanban
|
<Kanban
|
||||||
bind:this={kanbanUI}
|
bind:this={kanbanUI}
|
||||||
_class={tracker.class.Issue}
|
_class={tracker.class.Issue}
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
<Spinner size="small" />
|
<Spinner size="small" />
|
||||||
{/if}
|
{/if}
|
||||||
<span class="overflow-label issue-title">{issue.title}</span>
|
<span class="overflow-label issue-title">{issue.title}</span>
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
class="button-close"
|
class="button-close"
|
||||||
use:tooltip={{ label: tracker.string.RemoveParent, direction: 'bottom' }}
|
use:tooltip={{ label: tracker.string.RemoveParent, direction: 'bottom' }}
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
<div class="root" style:max-width={maxWidth}>
|
<div class="root" style:max-width={maxWidth}>
|
||||||
<span class="names">
|
<span class="names">
|
||||||
{#each value.parents as parentInfo}
|
{#each value.parents as parentInfo}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<span class="name cursor-pointer" on:click={() => handleIssueEditorOpened(parentInfo)}
|
<span class="name cursor-pointer" on:click={() => handleIssueEditorOpened(parentInfo)}
|
||||||
>{parentInfo.parentTitle}</span
|
>{parentInfo.parentTitle}</span
|
||||||
>
|
>
|
||||||
|
@ -73,6 +73,7 @@
|
|||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
{#if kind === 'list' || kind === 'list-header'}
|
{#if kind === 'list' || kind === 'list-header'}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="priority-container" on:click={handlePriorityEditorOpened}>
|
<div class="priority-container" on:click={handlePriorityEditorOpened}>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
{#if issuePriorities[value.priority]?.icon}<Icon icon={issuePriorities[value.priority]?.icon} {size} />{/if}
|
{#if issuePriorities[value.priority]?.icon}<Icon icon={issuePriorities[value.priority]?.icon} {size} />{/if}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AttachedData, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
|
import { AttachedData, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
|
||||||
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||||
import { Button, eventToHTMLElement, SelectPopup, showPopup, TooltipAlignment } from '@hcengineering/ui'
|
import { Button, eventToHTMLElement, SelectPopup, showPopup, TooltipAlignment } from '@hcengineering/ui'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
@ -34,6 +34,9 @@
|
|||||||
export let justify: 'left' | 'center' = 'left'
|
export let justify: 'left' | 'center' = 'left'
|
||||||
export let width: string | undefined = undefined
|
export let width: string | undefined = undefined
|
||||||
|
|
||||||
|
// Extra properties
|
||||||
|
export let issueStatuses: Map<Ref<Team>, WithLookup<IssueStatus>[]> | undefined = undefined
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const statusesQuery = createQuery()
|
const statusesQuery = createQuery()
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
@ -65,7 +68,7 @@
|
|||||||
|
|
||||||
$: selectedStatus = statuses?.find((status) => status._id === value.status) ?? statuses?.[0]
|
$: selectedStatus = statuses?.find((status) => status._id === value.status) ?? statuses?.[0]
|
||||||
$: selectedStatusLabel = shouldShowLabel ? selectedStatus?.name : undefined
|
$: selectedStatusLabel = shouldShowLabel ? selectedStatus?.name : undefined
|
||||||
$: statusesInfo = statuses?.map((s, i) => {
|
$: statusesInfo = statuses?.map((s) => {
|
||||||
return {
|
return {
|
||||||
id: s._id,
|
id: s._id,
|
||||||
component: StatusPresenter,
|
component: StatusPresenter,
|
||||||
@ -74,18 +77,23 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
$: if (!statuses) {
|
$: if (!statuses) {
|
||||||
const query = '_id' in value ? { attachedTo: value.space } : {}
|
statuses = '_id' in value ? issueStatuses?.get(value.space) : undefined
|
||||||
statusesQuery.query(
|
if (statuses === undefined) {
|
||||||
tracker.class.IssueStatus,
|
const query = '_id' in value ? { attachedTo: value.space } : {}
|
||||||
query,
|
statusesQuery.query(
|
||||||
(result) => {
|
tracker.class.IssueStatus,
|
||||||
statuses = result
|
query,
|
||||||
},
|
(result) => {
|
||||||
{
|
statuses = result
|
||||||
lookup: { category: tracker.class.IssueStatusCategory },
|
},
|
||||||
sort: { rank: SortingOrder.Ascending }
|
{
|
||||||
}
|
lookup: { category: tracker.class.IssueStatusCategory },
|
||||||
)
|
sort: { rank: SortingOrder.Ascending }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
statusesQuery.unsubscribe()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$: smallgap = size === 'inline' || size === 'small'
|
$: smallgap = size === 'inline' || size === 'small'
|
||||||
</script>
|
</script>
|
||||||
@ -95,7 +103,11 @@
|
|||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="flex-row-center flex-no-shrink" class:cursor-pointer={isEditable} on:click={handleStatusEditorOpened}>
|
<div class="flex-row-center flex-no-shrink" class:cursor-pointer={isEditable} on:click={handleStatusEditorOpened}>
|
||||||
<div class="flex-center flex-no-shrink square-4">
|
<div class="flex-center flex-no-shrink square-4">
|
||||||
{#if selectedStatus}<IssueStatusIcon value={selectedStatus} size={kind === 'list' ? 'inline' : 'medium'} />{/if}
|
{#if selectedStatus}<IssueStatusIcon
|
||||||
|
value={selectedStatus}
|
||||||
|
issueStatuses={statuses}
|
||||||
|
size={kind === 'list' ? 'inline' : 'medium'}
|
||||||
|
/>{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if selectedStatusLabel}
|
{#if selectedStatusLabel}
|
||||||
<span
|
<span
|
||||||
|
@ -14,12 +14,13 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Issue } from '@hcengineering/tracker'
|
import type { Issue } from '@hcengineering/tracker'
|
||||||
import ParentNamesPresenter from './ParentNamesPresenter.svelte'
|
|
||||||
import tracker from '../../plugin'
|
|
||||||
import { showPanel } from '@hcengineering/ui'
|
import { showPanel } from '@hcengineering/ui'
|
||||||
|
import tracker from '../../plugin'
|
||||||
|
import ParentNamesPresenter from './ParentNamesPresenter.svelte'
|
||||||
|
|
||||||
export let value: Issue
|
export let value: Issue
|
||||||
export let shouldUseMargin: boolean = false
|
export let shouldUseMargin: boolean = false
|
||||||
|
export let showParent = true
|
||||||
|
|
||||||
function handleIssueEditorOpened () {
|
function handleIssueEditorOpened () {
|
||||||
showPanel(tracker.component.EditIssue, value._id, value._class, 'content')
|
showPanel(tracker.component.EditIssue, value._id, value._class, 'content')
|
||||||
@ -28,12 +29,15 @@
|
|||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
<span class="titlePresenter-container" class:with-margin={shouldUseMargin} title={value.title}>
|
<span class="titlePresenter-container" class:with-margin={shouldUseMargin} title={value.title}>
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<span
|
<span
|
||||||
class="name overflow-label cursor-pointer"
|
class="name overflow-label cursor-pointer"
|
||||||
style={`max-width: ${value.parents.length !== 0 ? 95 : 100}%`}
|
style:max-width={showParent ? `${value.parents.length !== 0 ? 95 : 100}%` : '100%'}
|
||||||
on:click={handleIssueEditorOpened}>{value.title}</span
|
on:click={handleIssueEditorOpened}>{value.title}</span
|
||||||
>
|
>
|
||||||
<ParentNamesPresenter {value} />
|
{#if showParent}
|
||||||
|
<ParentNamesPresenter {value} />
|
||||||
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -241,6 +241,7 @@
|
|||||||
<span class="title select-text">{title}</span>
|
<span class="title select-text">{title}</span>
|
||||||
<div class="mt-6 description-preview select-text">
|
<div class="mt-6 description-preview select-text">
|
||||||
{#if isDescriptionEmpty}
|
{#if isDescriptionEmpty}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="placeholder" on:click={edit}>
|
<div class="placeholder" on:click={edit}>
|
||||||
<Label label={tracker.string.IssueDescriptionPlaceholder} />
|
<Label label={tracker.string.IssueDescriptionPlaceholder} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,65 +13,20 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, WithLookup } from '@hcengineering/core'
|
import { DocumentQuery, Ref, WithLookup } from '@hcengineering/core'
|
||||||
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
|
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
|
||||||
import { getEventPositionElement, showPanel, showPopup } from '@hcengineering/ui'
|
import { Viewlet, ViewOptions } from '@hcengineering/view'
|
||||||
import { ActionContext, ContextMenu, FixedColumn } from '@hcengineering/view-resources'
|
import { ActionContext, List } from '@hcengineering/view-resources'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import { flip } from 'svelte/animate'
|
|
||||||
import { getIssueId } from '../../../issues'
|
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import { subIssueListProvider } from '../../../utils'
|
|
||||||
import Circles from '../../icons/Circles.svelte'
|
|
||||||
import AssigneeEditor from '../AssigneeEditor.svelte'
|
|
||||||
import DueDateEditor from '../DueDateEditor.svelte'
|
|
||||||
import PriorityEditor from '../PriorityEditor.svelte'
|
|
||||||
import StatusEditor from '../StatusEditor.svelte'
|
|
||||||
import EstimationEditor from '../timereport/EstimationEditor.svelte'
|
|
||||||
import SubIssuesSelector from './SubIssuesSelector.svelte'
|
|
||||||
|
|
||||||
export let issues: Issue[]
|
export let query: DocumentQuery<Issue> | undefined = undefined
|
||||||
|
export let issues: Issue[] | undefined = undefined
|
||||||
|
export let viewlet: Viewlet
|
||||||
|
export let viewOptions: ViewOptions
|
||||||
|
|
||||||
export let teams: Map<Ref<Team>, Team>
|
// Extra properties
|
||||||
|
export let teams: Map<Ref<Team>, Team> | undefined
|
||||||
export let issueStatuses: Map<Ref<Team>, WithLookup<IssueStatus>[]>
|
export let issueStatuses: Map<Ref<Team>, WithLookup<IssueStatus>[]>
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
let draggingIndex: number | null = null
|
|
||||||
let hoveringIndex: number | null = null
|
|
||||||
|
|
||||||
function openIssue (target: Issue) {
|
|
||||||
dispatch('issue-focus', target)
|
|
||||||
subIssueListProvider(issues, target._id)
|
|
||||||
showPanel(tracker.component.EditIssue, target._id, target._class, 'content')
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetDrag () {
|
|
||||||
draggingIndex = null
|
|
||||||
hoveringIndex = null
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDragStart (ev: DragEvent, index: number) {
|
|
||||||
if (ev.dataTransfer) {
|
|
||||||
ev.dataTransfer.effectAllowed = 'move'
|
|
||||||
ev.dataTransfer.dropEffect = 'move'
|
|
||||||
draggingIndex = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDrop (ev: DragEvent, toIndex: number) {
|
|
||||||
if (ev.dataTransfer && draggingIndex !== null && toIndex !== draggingIndex) {
|
|
||||||
ev.dataTransfer.dropEffect = 'move'
|
|
||||||
|
|
||||||
dispatch('move', { fromIndex: draggingIndex, toIndex })
|
|
||||||
}
|
|
||||||
|
|
||||||
resetDrag()
|
|
||||||
}
|
|
||||||
|
|
||||||
function showContextMenu (ev: MouseEvent, object: Issue) {
|
|
||||||
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionContext
|
<ActionContext
|
||||||
@ -80,136 +35,15 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#each issues as issue, index (issue._id)}
|
{#if viewlet}
|
||||||
{@const currentTeam = teams.get(issue.space)}
|
<List
|
||||||
{@const openIssueCall = () => openIssue(issue)}
|
_class={tracker.class.Issue}
|
||||||
<div
|
{viewOptions}
|
||||||
class="flex-between row"
|
viewOptionsConfig={viewlet.viewOptions?.other}
|
||||||
class:is-dragging={index === draggingIndex}
|
config={viewlet.config}
|
||||||
class:is-dragged-over-up={draggingIndex !== null && index < draggingIndex && index === hoveringIndex}
|
documents={issues}
|
||||||
class:is-dragged-over-down={draggingIndex !== null && index > draggingIndex && index === hoveringIndex}
|
{query}
|
||||||
animate:flip={{ duration: 400 }}
|
flatHeaders={true}
|
||||||
draggable={true}
|
props={{ teams, issueStatuses }}
|
||||||
on:click|self={openIssueCall}
|
/>
|
||||||
on:contextmenu|preventDefault={(ev) => showContextMenu(ev, issue)}
|
{/if}
|
||||||
on:dragstart={(ev) => handleDragStart(ev, index)}
|
|
||||||
on:dragover|preventDefault={() => false}
|
|
||||||
on:dragenter={() => (hoveringIndex = index)}
|
|
||||||
on:drop|preventDefault={(ev) => handleDrop(ev, index)}
|
|
||||||
on:dragend={resetDrag}
|
|
||||||
>
|
|
||||||
<div class="draggable-container">
|
|
||||||
<div class="draggable-mark"><Circles /></div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-row-center ml-6 clear-mins gap-2">
|
|
||||||
<PriorityEditor value={issue} isEditable kind={'list'} size={'small'} justify={'center'} />
|
|
||||||
<span class="issuePresenter" on:click={openIssueCall}>
|
|
||||||
<FixedColumn key={'subissue_issue'} justify={'left'}>
|
|
||||||
{#if currentTeam}
|
|
||||||
{getIssueId(currentTeam, issue)}
|
|
||||||
{/if}
|
|
||||||
</FixedColumn>
|
|
||||||
</span>
|
|
||||||
<StatusEditor
|
|
||||||
value={issue}
|
|
||||||
statuses={issueStatuses.get(issue.space)}
|
|
||||||
justify="center"
|
|
||||||
kind={'list'}
|
|
||||||
size={'small'}
|
|
||||||
tooltipAlignment="bottom"
|
|
||||||
/>
|
|
||||||
<span class="text name" title={issue.title} on:click={openIssueCall}>
|
|
||||||
{issue.title}
|
|
||||||
</span>
|
|
||||||
{#if issue.subIssues > 0}
|
|
||||||
<SubIssuesSelector value={issue} {currentTeam} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="flex-center flex-no-shrink">
|
|
||||||
<EstimationEditor value={issue} kind={'list'} />
|
|
||||||
{#if issue.dueDate !== null}
|
|
||||||
<DueDateEditor value={issue} />
|
|
||||||
{/if}
|
|
||||||
<AssigneeEditor value={issue} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.row {
|
|
||||||
position: relative;
|
|
||||||
border-bottom: 1px solid var(--divider-color);
|
|
||||||
|
|
||||||
.text {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--caption-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.issuePresenter {
|
|
||||||
flex-shrink: 0;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--content-color);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--caption-color);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.draggable-container {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
width: 1.5rem;
|
|
||||||
cursor: grabbing;
|
|
||||||
|
|
||||||
.draggable-mark {
|
|
||||||
opacity: 0;
|
|
||||||
width: 0.375rem;
|
|
||||||
height: 1rem;
|
|
||||||
margin-left: 0.75rem;
|
|
||||||
transition: opacity 0.1s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.draggable-mark {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-dragging::before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
background-color: var(--theme-bg-color);
|
|
||||||
opacity: 0.4;
|
|
||||||
inset: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-dragged-over-up::before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
inset: 0;
|
|
||||||
border-top: 1px solid var(--theme-bg-check);
|
|
||||||
}
|
|
||||||
&.is-dragged-over-down::before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
inset: 0;
|
|
||||||
border-bottom: 1px solid var(--theme-bg-check);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -92,7 +92,7 @@
|
|||||||
|
|
||||||
$: areSubIssuesLoading = !subIssues
|
$: areSubIssuesLoading = !subIssues
|
||||||
$: parentIssue = issue.$lookup?.attachedTo ? (issue.$lookup?.attachedTo as Issue) : null
|
$: parentIssue = issue.$lookup?.attachedTo ? (issue.$lookup?.attachedTo as Issue) : null
|
||||||
$: if (parentIssue) {
|
$: if (parentIssue && parentIssue.subIssues > 0) {
|
||||||
subIssuesQeury.query(
|
subIssuesQeury.query(
|
||||||
tracker.class.Issue,
|
tracker.class.Issue,
|
||||||
{ space: issue.space, attachedTo: parentIssue._id },
|
{ space: issue.space, attachedTo: parentIssue._id },
|
||||||
@ -113,6 +113,7 @@
|
|||||||
{#if parentIssue}
|
{#if parentIssue}
|
||||||
<div class="flex root">
|
<div class="flex root">
|
||||||
<div class="item clear-mins">
|
<div class="item clear-mins">
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
class="flex-center parent-issue cursor-pointer"
|
class="flex-center parent-issue cursor-pointer"
|
||||||
use:tooltip={{ label: tracker.string.OpenParent, direction: 'bottom' }}
|
use:tooltip={{ label: tracker.string.OpenParent, direction: 'bottom' }}
|
||||||
@ -136,6 +137,7 @@
|
|||||||
<Spinner size="small" />
|
<Spinner size="small" />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
bind:this={subIssuesElement}
|
bind:this={subIssuesElement}
|
||||||
class="flex-center sub-issues cursor-pointer"
|
class="flex-center sub-issues cursor-pointer"
|
||||||
|
@ -14,9 +14,11 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, SortingOrder, WithLookup } from '@hcengineering/core'
|
import { Ref, SortingOrder, WithLookup } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import { calcRank, Issue, IssueStatus, Team } from '@hcengineering/tracker'
|
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
|
||||||
import { Button, Spinner, ExpandCollapse, closeTooltip, IconAdd, Chevron, Label } from '@hcengineering/ui'
|
import { Button, Chevron, closeTooltip, ExpandCollapse, IconAdd, Label } from '@hcengineering/ui'
|
||||||
|
import view, { Viewlet } from '@hcengineering/view'
|
||||||
|
import { getViewOptions, ViewletSettingButton } from '@hcengineering/view-resources'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import CreateSubIssue from './CreateSubIssue.svelte'
|
import CreateSubIssue from './CreateSubIssue.svelte'
|
||||||
import SubIssueList from './SubIssueList.svelte'
|
import SubIssueList from './SubIssueList.svelte'
|
||||||
@ -25,35 +27,54 @@
|
|||||||
export let teams: Map<Ref<Team>, Team>
|
export let teams: Map<Ref<Team>, Team>
|
||||||
export let issueStatuses: Map<Ref<Team>, WithLookup<IssueStatus>[]>
|
export let issueStatuses: Map<Ref<Team>, WithLookup<IssueStatus>[]>
|
||||||
|
|
||||||
const subIssuesQuery = createQuery()
|
|
||||||
const client = getClient()
|
|
||||||
|
|
||||||
let subIssues: Issue[] | undefined
|
|
||||||
let isCollapsed = false
|
let isCollapsed = false
|
||||||
let isCreating = false
|
let isCreating = false
|
||||||
|
|
||||||
async function handleIssueSwap (ev: CustomEvent<{ fromIndex: number; toIndex: number }>) {
|
$: hasSubIssues = issue.subIssues > 0
|
||||||
if (subIssues) {
|
|
||||||
const { fromIndex, toIndex } = ev.detail
|
|
||||||
const [prev, next] = [
|
|
||||||
subIssues[fromIndex < toIndex ? toIndex : toIndex - 1],
|
|
||||||
subIssues[fromIndex < toIndex ? toIndex + 1 : toIndex]
|
|
||||||
]
|
|
||||||
const issue = subIssues[fromIndex]
|
|
||||||
|
|
||||||
await client.update(issue, { rank: calcRank(prev, next) })
|
let viewlet: Viewlet | undefined
|
||||||
}
|
|
||||||
|
const query = createQuery()
|
||||||
|
$: query.query(view.class.Viewlet, { _id: tracker.viewlet.SubIssues }, (res) => {
|
||||||
|
;[viewlet] = res
|
||||||
|
})
|
||||||
|
|
||||||
|
let _teams = teams
|
||||||
|
let _issueStatuses = issueStatuses
|
||||||
|
|
||||||
|
const teamsQuery = createQuery()
|
||||||
|
|
||||||
|
$: if (teams === undefined) {
|
||||||
|
teamsQuery.query(tracker.class.Team, {}, async (result) => {
|
||||||
|
_teams = new Map(result.map((it) => [it._id, it]))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
teamsQuery.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
$: hasSubIssues = issue.subIssues > 0
|
const statusesQuery = createQuery()
|
||||||
$: subIssuesQuery.query(tracker.class.Issue, { attachedTo: issue._id }, async (result) => (subIssues = result), {
|
$: if (issueStatuses === undefined) {
|
||||||
sort: { rank: SortingOrder.Ascending },
|
statusesQuery.query(
|
||||||
lookup: {
|
tracker.class.IssueStatus,
|
||||||
_id: {
|
{},
|
||||||
subIssues: tracker.class.Issue
|
(statuses) => {
|
||||||
|
const st = new Map<Ref<Team>, WithLookup<IssueStatus>[]>()
|
||||||
|
for (const s of statuses) {
|
||||||
|
const id = s.attachedTo as Ref<Team>
|
||||||
|
st.set(id, [...(st.get(id) ?? []), s])
|
||||||
|
}
|
||||||
|
_issueStatuses = st
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lookup: { category: tracker.class.IssueStatusCategory },
|
||||||
|
sort: { rank: SortingOrder.Ascending }
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
})
|
} else {
|
||||||
|
statusesQuery.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
$: viewOptions = viewlet !== undefined ? getViewOptions(viewlet) : undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-between">
|
<div class="flex-between">
|
||||||
@ -73,38 +94,42 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="flex-row-center">
|
||||||
<Button
|
{#if viewlet && hasSubIssues && viewOptions}
|
||||||
id="add-sub-issue"
|
<ViewletSettingButton bind:viewOptions {viewlet} kind={'transparent'} />
|
||||||
width="min-content"
|
{/if}
|
||||||
icon={hasSubIssues ? IconAdd : undefined}
|
<Button
|
||||||
label={hasSubIssues ? undefined : tracker.string.AddSubIssues}
|
id="add-sub-issue"
|
||||||
labelParams={{ subIssues: 0 }}
|
width="min-content"
|
||||||
kind={'transparent'}
|
icon={hasSubIssues ? IconAdd : undefined}
|
||||||
size={'small'}
|
label={hasSubIssues ? undefined : tracker.string.AddSubIssues}
|
||||||
showTooltip={{ label: tracker.string.AddSubIssues, props: { subIssues: 1 }, direction: 'bottom' }}
|
labelParams={{ subIssues: 0 }}
|
||||||
on:click={() => {
|
kind={'transparent'}
|
||||||
closeTooltip()
|
size={'small'}
|
||||||
isCreating = true
|
showTooltip={{ label: tracker.string.AddSubIssues, props: { subIssues: 1 }, direction: 'bottom' }}
|
||||||
isCollapsed = false
|
on:click={() => {
|
||||||
}}
|
closeTooltip()
|
||||||
/>
|
isCreating = true
|
||||||
|
isCollapsed = false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1">
|
<div class="mt-1">
|
||||||
{#if subIssues && issueStatuses}
|
{#if issueStatuses}
|
||||||
<ExpandCollapse isExpanded={!isCollapsed} duration={400}>
|
{#if hasSubIssues && viewOptions && viewlet}
|
||||||
{#if hasSubIssues}
|
<ExpandCollapse isExpanded={!isCollapsed} duration={400}>
|
||||||
<div class="list" class:collapsed={isCollapsed}>
|
<div class="list" class:collapsed={isCollapsed}>
|
||||||
<SubIssueList
|
<SubIssueList
|
||||||
issues={subIssues}
|
teams={_teams}
|
||||||
{issueStatuses}
|
{viewlet}
|
||||||
{teams}
|
{viewOptions}
|
||||||
on:issue-focus={() => (isCreating = false)}
|
issueStatuses={_issueStatuses}
|
||||||
on:move={handleIssueSwap}
|
query={{ attachedTo: issue._id }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</ExpandCollapse>
|
||||||
</ExpandCollapse>
|
{/if}
|
||||||
<ExpandCollapse isExpanded={!isCollapsed} duration={400}>
|
<ExpandCollapse isExpanded={!isCollapsed} duration={400}>
|
||||||
{#if isCreating}
|
{#if isCreating}
|
||||||
{@const team = teams.get(issue.space)}
|
{@const team = teams.get(issue.space)}
|
||||||
@ -121,10 +146,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</ExpandCollapse>
|
</ExpandCollapse>
|
||||||
{:else}
|
|
||||||
<div class="flex-center pt-3">
|
|
||||||
<Spinner />
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -17,10 +17,13 @@
|
|||||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { calcRank, Issue, IssueStatus, Team } from '@hcengineering/tracker'
|
import { calcRank, Issue, IssueStatus, Team } from '@hcengineering/tracker'
|
||||||
import { Label, Spinner } from '@hcengineering/ui'
|
import { Label, Spinner } from '@hcengineering/ui'
|
||||||
|
import { Viewlet, ViewOptions } from '@hcengineering/view'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import SubIssueList from '../edit/SubIssueList.svelte'
|
import SubIssueList from '../edit/SubIssueList.svelte'
|
||||||
|
|
||||||
export let object: Doc
|
export let object: Doc
|
||||||
|
export let viewlet: Viewlet
|
||||||
|
export let viewOptions: ViewOptions
|
||||||
|
|
||||||
let query: DocumentQuery<Issue>
|
let query: DocumentQuery<Issue>
|
||||||
$: query = { 'relations._id': object._id, 'relations._class': object._class }
|
$: query = { 'relations._id': object._id, 'relations._class': object._class }
|
||||||
@ -75,9 +78,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mt-1">
|
<div class="mt-1">
|
||||||
{#if subIssues !== undefined}
|
{#if subIssues !== undefined && viewlet !== undefined}
|
||||||
{#if issueStatuses.size > 0 && teams}
|
{#if issueStatuses.size > 0 && teams}
|
||||||
<SubIssueList issues={subIssues} {teams} {issueStatuses} on:move={handleIssueSwap} />
|
<SubIssueList bind:viewOptions {viewlet} issues={subIssues} {teams} {issueStatuses} on:move={handleIssueSwap} />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
<Label label={presentation.string.NoMatchesFound} />
|
<Label label={presentation.string.NoMatchesFound} />
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Doc } from '@hcengineering/core'
|
||||||
|
import { IntlString } from '@hcengineering/platform'
|
||||||
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
|
import { Button, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||||
|
import view, { Viewlet } from '@hcengineering/view'
|
||||||
|
import { getViewOptions, ViewletSettingButton } from '@hcengineering/view-resources'
|
||||||
|
import tracker from '../../../plugin'
|
||||||
|
import RelatedIssues from './RelatedIssues.svelte'
|
||||||
|
export let object: Doc
|
||||||
|
export let label: IntlString
|
||||||
|
|
||||||
|
let viewlet: Viewlet | undefined
|
||||||
|
|
||||||
|
const vquery = createQuery()
|
||||||
|
$: vquery.query(view.class.Viewlet, { _id: tracker.viewlet.SubIssues }, (res) => {
|
||||||
|
;[viewlet] = res
|
||||||
|
})
|
||||||
|
|
||||||
|
let viewOptions = getViewOptions(viewlet)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="antiSection">
|
||||||
|
<div class="antiSection-header">
|
||||||
|
<div class="antiSection-header__icon">
|
||||||
|
<Icon icon={tracker.icon.Issue} size={'small'} />
|
||||||
|
</div>
|
||||||
|
<span class="antiSection-header__title">
|
||||||
|
<Label {label} />
|
||||||
|
</span>
|
||||||
|
<div class="buttons-group small-gap">
|
||||||
|
{#if viewlet && viewOptions}
|
||||||
|
<ViewletSettingButton bind:viewOptions {viewlet} kind={'transparent'} />
|
||||||
|
{/if}
|
||||||
|
<Button
|
||||||
|
id="add-sub-issue"
|
||||||
|
width="min-content"
|
||||||
|
icon={IconAdd}
|
||||||
|
label={undefined}
|
||||||
|
labelParams={{ subIssues: 0 }}
|
||||||
|
kind={'transparent'}
|
||||||
|
size={'small'}
|
||||||
|
on:click={() => {
|
||||||
|
showPopup(tracker.component.CreateIssue, { relatedTo: object, space: object.space }, 'top')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row">
|
||||||
|
{#if viewlet}
|
||||||
|
<RelatedIssues {object} {viewOptions} {viewlet} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -139,7 +139,7 @@
|
|||||||
<Button
|
<Button
|
||||||
icon={IconAdd}
|
icon={IconAdd}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
on:click={(event) => {
|
on:click={() => {
|
||||||
showPopup(
|
showPopup(
|
||||||
TimeSpendReportPopup,
|
TimeSpendReportPopup,
|
||||||
{
|
{
|
||||||
|
@ -14,11 +14,11 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import { Doc, Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { UserBox } from '@hcengineering/presentation'
|
import { UserBox } from '@hcengineering/presentation'
|
||||||
import { Issue, Team } from '@hcengineering/tracker'
|
import { Issue, Team } from '@hcengineering/tracker'
|
||||||
import { getEventPositionElement, ListView, showPopup, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
import { deviceOptionsStore as deviceInfo, getEventPositionElement, ListView, showPopup } from '@hcengineering/ui'
|
||||||
import { ContextMenu, FixedColumn, ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
|
import { ContextMenu, FixedColumn, ListSelectionProvider } from '@hcengineering/view-resources'
|
||||||
import { getIssueId } from '../../../issues'
|
import { getIssueId } from '../../../issues'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import EstimationEditor from './EstimationEditor.svelte'
|
import EstimationEditor from './EstimationEditor.svelte'
|
||||||
@ -31,7 +31,7 @@
|
|||||||
showPopup(ContextMenu, { object }, $deviceInfo.isMobile ? 'top' : getEventPositionElement(ev))
|
showPopup(ContextMenu, { object }, $deviceInfo.isMobile ? 'top' : getEventPositionElement(ev))
|
||||||
}
|
}
|
||||||
|
|
||||||
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {})
|
const listProvider = new ListSelectionProvider(() => {})
|
||||||
$: twoRows = $deviceInfo.twoRows
|
$: twoRows = $deviceInfo.twoRows
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if kind === 'link'}
|
{#if kind === 'link'}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div id="ReportedTimeEditor" class="link-container flex-between" on:click={showReports}>
|
<div id="ReportedTimeEditor" class="link-container flex-between" on:click={showReports}>
|
||||||
{#if value !== undefined}
|
{#if value !== undefined}
|
||||||
<span class="overflow-label">
|
<span class="overflow-label">
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
export let value: number
|
export let value: number
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<span
|
<span
|
||||||
{id}
|
{id}
|
||||||
class:link={kind === 'link'}
|
class:link={kind === 'link'}
|
||||||
|
@ -14,13 +14,18 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import contact from '@hcengineering/contact'
|
import contact from '@hcengineering/contact'
|
||||||
import { Doc, Ref, Space, WithLookup } from '@hcengineering/core'
|
import { Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import UserBox from '@hcengineering/presentation/src/components/UserBox.svelte'
|
import UserBox from '@hcengineering/presentation/src/components/UserBox.svelte'
|
||||||
import { Team, TimeReportDayType, TimeSpendReport } from '@hcengineering/tracker'
|
import { Team, TimeReportDayType, TimeSpendReport } from '@hcengineering/tracker'
|
||||||
import { eventToHTMLElement, getEventPositionElement, ListView, showPopup } from '@hcengineering/ui'
|
import {
|
||||||
import { deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
deviceOptionsStore as deviceInfo,
|
||||||
|
eventToHTMLElement,
|
||||||
|
getEventPositionElement,
|
||||||
|
ListView,
|
||||||
|
showPopup
|
||||||
|
} from '@hcengineering/ui'
|
||||||
import DatePresenter from '@hcengineering/ui/src/components/calendar/DatePresenter.svelte'
|
import DatePresenter from '@hcengineering/ui/src/components/calendar/DatePresenter.svelte'
|
||||||
import { ContextMenu, FixedColumn, ListSelectionProvider, SelectDirection } from '@hcengineering/view-resources'
|
import { ContextMenu, FixedColumn, ListSelectionProvider } from '@hcengineering/view-resources'
|
||||||
import { getIssueId } from '../../../issues'
|
import { getIssueId } from '../../../issues'
|
||||||
import tracker from '../../../plugin'
|
import tracker from '../../../plugin'
|
||||||
import TimePresenter from './TimePresenter.svelte'
|
import TimePresenter from './TimePresenter.svelte'
|
||||||
@ -34,7 +39,7 @@
|
|||||||
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
|
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
|
||||||
}
|
}
|
||||||
|
|
||||||
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {})
|
const listProvider = new ListSelectionProvider(() => {})
|
||||||
|
|
||||||
const toTeamId = (ref: Ref<Space>) => ref as Ref<Team>
|
const toTeamId = (ref: Ref<Space>) => ref as Ref<Team>
|
||||||
|
|
||||||
|
@ -25,9 +25,9 @@
|
|||||||
import ModeSelector from '../ModeSelector.svelte'
|
import ModeSelector from '../ModeSelector.svelte'
|
||||||
|
|
||||||
const config: [string, IntlString, object][] = [
|
const config: [string, IntlString, object][] = [
|
||||||
['assigned', tracker.string.Assigned],
|
['assigned', tracker.string.Assigned, {}],
|
||||||
['created', tracker.string.Created, { value: 0 }],
|
['created', tracker.string.Created, { value: 0 }],
|
||||||
['subscribed', tracker.string.Subscribed]
|
['subscribed', tracker.string.Subscribed, {}]
|
||||||
]
|
]
|
||||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||||
const assigned = { assignee: currentUser.employee }
|
const assigned = { assignee: currentUser.employee }
|
||||||
|
@ -86,12 +86,3 @@
|
|||||||
<DatePresenter bind:value={object.targetDate} labelNull={tracker.string.TargetDate} editable />
|
<DatePresenter bind:value={object.targetDate} labelNull={tracker.string.TargetDate} editable />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<style>
|
|
||||||
.label {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="flex-presenter flex-grow" on:click={navigateToProject}>
|
<div class="flex-presenter flex-grow" on:click={navigateToProject}>
|
||||||
<span title={value.label} class="projectLabel flex-grow">{value.label}</span>
|
<span title={value.label} class="projectLabel flex-grow">{value.label}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Issue, Project } from '@hcengineering/tracker'
|
import { Issue, Project } from '@hcengineering/tracker'
|
||||||
import { CheckBox, Spinner, tooltip } from '@hcengineering/ui'
|
import { CheckBox, Spinner, tooltip } from '@hcengineering/ui'
|
||||||
import { AttributeModel, BuildModelKey } from '@hcengineering/view'
|
import { BuildModelKey } from '@hcengineering/view'
|
||||||
import { buildModel, getObjectPresenter, LoadingProps } from '@hcengineering/view-resources'
|
import { buildModel, LoadingProps } from '@hcengineering/view-resources'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
|
||||||
@ -42,16 +42,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let personPresenter: AttributeModel
|
|
||||||
|
|
||||||
$: options = { ...baseOptions } as FindOptions<Project>
|
$: options = { ...baseOptions } as FindOptions<Project>
|
||||||
$: selectedObjectIdsSet = new Set<Ref<Doc>>(selectedObjectIds.map((it) => it._id))
|
$: selectedObjectIdsSet = new Set<Ref<Doc>>(selectedObjectIds.map((it) => it._id))
|
||||||
$: objectRefs.length = projects?.length ?? 0
|
$: objectRefs.length = projects?.length ?? 0
|
||||||
|
|
||||||
$: getObjectPresenter(client, contact.class.Person, { key: '' }).then((p) => {
|
|
||||||
personPresenter = p
|
|
||||||
})
|
|
||||||
|
|
||||||
export const onObjectChecked = (docs: Doc[], value: boolean) => {
|
export const onObjectChecked = (docs: Doc[], value: boolean) => {
|
||||||
dispatch('check', { docs, value })
|
dispatch('check', { docs, value })
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@
|
|||||||
await client.update(sprint, { [field]: value })
|
await client.update(sprint, { [field]: value })
|
||||||
}
|
}
|
||||||
let container: HTMLElement
|
let container: HTMLElement
|
||||||
function selectSprint (evt: MouseEvent): void {
|
|
||||||
|
function selectSprint (): void {
|
||||||
showPopup(SprintPopup, { _class: tracker.class.Sprint }, container, (value) => {
|
showPopup(SprintPopup, { _class: tracker.class.Sprint }, container, (value) => {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
sprint = value
|
sprint = value
|
||||||
|
@ -104,12 +104,3 @@
|
|||||||
/>
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<style>
|
|
||||||
.label {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.description {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -123,9 +123,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.showWarning {
|
|
||||||
color: var(--warning-color) !important;
|
|
||||||
}
|
|
||||||
.minus-margin {
|
.minus-margin {
|
||||||
margin-left: -0.5rem;
|
margin-left: -0.5rem;
|
||||||
&-vSpace {
|
&-vSpace {
|
||||||
|
@ -119,6 +119,7 @@
|
|||||||
<div class="listRoot">
|
<div class="listRoot">
|
||||||
{#if sprints}
|
{#if sprints}
|
||||||
{#each Array.from(byProject?.entries() ?? []) as e}
|
{#each Array.from(byProject?.entries() ?? []) as e}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="flex-between categoryHeader row" on:click={() => handleCollapseCategory(e[0])}>
|
<div class="flex-between categoryHeader row" on:click={() => handleCollapseCategory(e[0])}>
|
||||||
<div class="flex-row-center gap-2 clear-mins">
|
<div class="flex-row-center gap-2 clear-mins">
|
||||||
<SprintProjectEditor
|
<SprintProjectEditor
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="flex-presenter flex-grow" on:click={navigateToSprint}>
|
<div class="flex-presenter flex-grow" on:click={navigateToSprint}>
|
||||||
<span title={value.label} class="projectLabel flex-grow">{value.label}</span>
|
<span title={value.label} class="projectLabel flex-grow">{value.label}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -82,10 +82,6 @@
|
|||||||
resetDrag()
|
resetDrag()
|
||||||
}
|
}
|
||||||
|
|
||||||
function showContextMenu (ev: MouseEvent, object: IssueTemplateChild) {
|
|
||||||
// showPopup(ContextMenu, { object }, getEventPositionElement(ev))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getIssueTemplateId (team: string, issue: IssueTemplateChild): string {
|
export function getIssueTemplateId (team: string, issue: IssueTemplateChild): string {
|
||||||
return `${team}-${issues.findIndex((it) => it.id === issue.id)}`
|
return `${team}-${issues.findIndex((it) => it.id === issue.id)}`
|
||||||
}
|
}
|
||||||
@ -107,7 +103,6 @@
|
|||||||
animate:flip={{ duration: 400 }}
|
animate:flip={{ duration: 400 }}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
on:click|self={(evt) => openIssue(evt, issue)}
|
on:click|self={(evt) => openIssue(evt, issue)}
|
||||||
on:contextmenu|preventDefault={(ev) => showContextMenu(ev, issue)}
|
|
||||||
on:dragstart={(ev) => handleDragStart(ev, index)}
|
on:dragstart={(ev) => handleDragStart(ev, index)}
|
||||||
on:dragover|preventDefault={() => false}
|
on:dragover|preventDefault={() => false}
|
||||||
on:dragenter={() => (hoveringIndex = index)}
|
on:dragenter={() => (hoveringIndex = index)}
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<span class="issuePresenterRoot flex" class:noPointer={disableClick} on:click={handleIssueEditorOpened}>
|
<span class="issuePresenterRoot flex" class:noPointer={disableClick} on:click={handleIssueEditorOpened}>
|
||||||
<Icon icon={tracker.icon.Issues} size={'small'} />
|
<Icon icon={tracker.icon.Issues} size={'small'} />
|
||||||
<span class="ml-2">
|
<span class="ml-2">
|
||||||
|
@ -4,11 +4,10 @@
|
|||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { IssueTemplate } from '@hcengineering/tracker'
|
import { IssueTemplate } from '@hcengineering/tracker'
|
||||||
import { Button, IconAdd, IconDetails, IconDetailsFilled, showPopup } from '@hcengineering/ui'
|
import { Button, IconAdd, IconDetails, IconDetailsFilled, showPopup } from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewOptionModel } from '@hcengineering/view'
|
import view, { Viewlet } from '@hcengineering/view'
|
||||||
import { FilterBar, getActiveViewletId } from '@hcengineering/view-resources'
|
import { FilterBar, getActiveViewletId, getViewOptions } from '@hcengineering/view-resources'
|
||||||
import ViewletSettingButton from '@hcengineering/view-resources/src/components/ViewletSettingButton.svelte'
|
import ViewletSettingButton from '@hcengineering/view-resources/src/components/ViewletSettingButton.svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import { getDefaultViewOptionsTemplatesConfig } from '../../utils'
|
|
||||||
import IssuesHeader from '../issues/IssuesHeader.svelte'
|
import IssuesHeader from '../issues/IssuesHeader.svelte'
|
||||||
import CreateIssueTemplate from './CreateIssueTemplate.svelte'
|
import CreateIssueTemplate from './CreateIssueTemplate.svelte'
|
||||||
import IssueTemplatesContent from './IssueTemplatesContent.svelte'
|
import IssueTemplatesContent from './IssueTemplatesContent.svelte'
|
||||||
@ -16,7 +15,6 @@
|
|||||||
export let query: DocumentQuery<IssueTemplate> = {}
|
export let query: DocumentQuery<IssueTemplate> = {}
|
||||||
export let title: IntlString | undefined = undefined
|
export let title: IntlString | undefined = undefined
|
||||||
export let label: string = ''
|
export let label: string = ''
|
||||||
export let viewOptionsConfig: ViewOptionModel[] = getDefaultViewOptionsTemplatesConfig()
|
|
||||||
|
|
||||||
export let panelWidth: number = 0
|
export let panelWidth: number = 0
|
||||||
|
|
||||||
@ -70,6 +68,8 @@
|
|||||||
const showCreateDialog = async () => {
|
const showCreateDialog = async () => {
|
||||||
showPopup(CreateIssueTemplate, { targetElement: null }, 'top')
|
showPopup(CreateIssueTemplate, { targetElement: null }, 'top')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: viewOptions = getViewOptions(viewlet)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IssuesHeader {viewlets} {label} bind:viewlet bind:search showLabelSelector={$$slots.label_selector}>
|
<IssuesHeader {viewlets} {label} bind:viewlet bind:search showLabelSelector={$$slots.label_selector}>
|
||||||
@ -86,7 +86,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{#if viewlet}
|
{#if viewlet}
|
||||||
<ViewletSettingButton {viewlet} />
|
<ViewletSettingButton bind:viewOptions {viewlet} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if asideFloat && $$slots.aside}
|
{#if asideFloat && $$slots.aside}
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Doc, generateId, Ref, WithLookup } from '@hcengineering/core'
|
import { generateId, Ref, WithLookup } from '@hcengineering/core'
|
||||||
import { AttributeBarEditor, createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
import { AttributeBarEditor, getClient, KeyedAttribute } from '@hcengineering/presentation'
|
||||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||||
import type { IssueTemplate } from '@hcengineering/tracker'
|
import type { IssueTemplate } from '@hcengineering/tracker'
|
||||||
import { Component, Label } from '@hcengineering/ui'
|
import { Component, Label } from '@hcengineering/ui'
|
||||||
@ -27,14 +27,6 @@
|
|||||||
|
|
||||||
export let issue: WithLookup<IssueTemplate>
|
export let issue: WithLookup<IssueTemplate>
|
||||||
|
|
||||||
const query = createQuery()
|
|
||||||
let showIsBlocking = false
|
|
||||||
let blockedBy: Doc[]
|
|
||||||
$: query.query(tracker.class.Issue, { blockedBy: { _id: issue._id, _class: issue._class } }, (result) => {
|
|
||||||
blockedBy = result
|
|
||||||
showIsBlocking = result.length > 0
|
|
||||||
})
|
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
const hierarchy = client.getHierarchy()
|
const hierarchy = client.getHierarchy()
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
<div class="flex-no-shrink draggable-mark">
|
<div class="flex-no-shrink draggable-mark">
|
||||||
{#if !isSingle}<Circles />{/if}
|
{#if !isSingle}<Circles />{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="flex-no-shrink ml-2 color" on:click={pickColor}>
|
<div class="flex-no-shrink ml-2 color" on:click={pickColor}>
|
||||||
<div class="dot" style="background-color: {getPlatformColor(value.color ?? 0)}" />
|
<div class="dot" style="background-color: {getPlatformColor(value.color ?? 0)}" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
export let value: IssueStatus
|
export let value: IssueStatus
|
||||||
export let isDefault = false
|
export let isDefault = false
|
||||||
export let isSingle = true
|
export let isSingle = true
|
||||||
|
export let issueStatuses: IssueStatus[] | undefined = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
@ -37,7 +38,7 @@
|
|||||||
<Circles />
|
<Circles />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-no-shrink ml-2">
|
<div class="flex-no-shrink ml-2">
|
||||||
<IssueStatusIcon {value} size="small" />
|
<IssueStatusIcon {value} size="small" {issueStatuses} />
|
||||||
</div>
|
</div>
|
||||||
<span class="content-accent-color ml-2">{value.name}</span>
|
<span class="content-accent-color ml-2">{value.name}</span>
|
||||||
{#if value.description}
|
{#if value.description}
|
||||||
@ -48,10 +49,12 @@
|
|||||||
{#if isDefault}
|
{#if isDefault}
|
||||||
<Label label={tracker.string.Default} />
|
<Label label={tracker.string.Default} />
|
||||||
{:else if value.category === tracker.issueStatusCategory.Backlog || value.category === tracker.issueStatusCategory.Unstarted}
|
{:else if value.category === tracker.issueStatusCategory.Backlog || value.category === tracker.issueStatusCategory.Unstarted}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="btn" on:click|preventDefault={() => dispatch('default-update', value._id)}>
|
<div class="btn" on:click|preventDefault={() => dispatch('default-update', value._id)}>
|
||||||
<Label label={tracker.string.MakeDefault} />
|
<Label label={tracker.string.MakeDefault} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
class="btn"
|
class="btn"
|
||||||
use:tooltip={{ label: tracker.string.EditWorkflowStatus, direction: 'bottom' }}
|
use:tooltip={{ label: tracker.string.EditWorkflowStatus, direction: 'bottom' }}
|
||||||
@ -60,6 +63,7 @@
|
|||||||
<Icon icon={IconEdit} size="small" />
|
<Icon icon={IconEdit} size="small" />
|
||||||
</div>
|
</div>
|
||||||
{#if !isSingle}
|
{#if !isSingle}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
class="btn"
|
class="btn"
|
||||||
use:tooltip={{ label: tracker.string.DeleteWorkflowStatus, direction: 'bottom' }}
|
use:tooltip={{ label: tracker.string.DeleteWorkflowStatus, direction: 'bottom' }}
|
||||||
|
@ -295,6 +295,7 @@
|
|||||||
editingStatus = { ...detail, color: detail.color ?? category.color }
|
editingStatus = { ...detail, color: detail.color ?? category.color }
|
||||||
}}
|
}}
|
||||||
on:delete={deleteStatus}
|
on:delete={deleteStatus}
|
||||||
|
issueStatuses={workflowStatuses}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,6 +60,7 @@ import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
|
|||||||
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
|
||||||
import Views from './components/views/Views.svelte'
|
import Views from './components/views/Views.svelte'
|
||||||
import Statuses from './components/workflow/Statuses.svelte'
|
import Statuses from './components/workflow/Statuses.svelte'
|
||||||
|
import RelatedIssuesSection from './components/issues/related/RelatedIssuesSection.svelte'
|
||||||
import {
|
import {
|
||||||
getIssueId,
|
getIssueId,
|
||||||
getIssueTitle,
|
getIssueTitle,
|
||||||
@ -278,7 +279,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
CreateTeam,
|
CreateTeam,
|
||||||
TeamPresenter,
|
TeamPresenter,
|
||||||
IssueStatistics,
|
IssueStatistics,
|
||||||
StatusRefPresenter
|
StatusRefPresenter,
|
||||||
|
RelatedIssuesSection
|
||||||
},
|
},
|
||||||
completion: {
|
completion: {
|
||||||
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>
|
IssueQuery: async (client: Client, query: string, filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }) =>
|
||||||
|
@ -15,12 +15,15 @@
|
|||||||
import { Client, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
import { Client, Doc, DocumentQuery, Ref } from '@hcengineering/core'
|
||||||
import type { IntlString, Metadata, Resource } from '@hcengineering/platform'
|
import type { IntlString, Metadata, Resource } from '@hcengineering/platform'
|
||||||
import { mergeIds } from '@hcengineering/platform'
|
import { mergeIds } from '@hcengineering/platform'
|
||||||
import { AnyComponent } from '@hcengineering/ui'
|
|
||||||
import tracker, { trackerId } from '../../tracker/lib'
|
|
||||||
import { IssueDraft } from '@hcengineering/tracker'
|
import { IssueDraft } from '@hcengineering/tracker'
|
||||||
import { SortFunc } from '@hcengineering/view'
|
import { AnyComponent } from '@hcengineering/ui'
|
||||||
|
import { SortFunc, Viewlet } from '@hcengineering/view'
|
||||||
|
import tracker, { trackerId } from '../../tracker/lib'
|
||||||
|
|
||||||
export default mergeIds(trackerId, tracker, {
|
export default mergeIds(trackerId, tracker, {
|
||||||
|
viewlet: {
|
||||||
|
SubIssues: '' as Ref<Viewlet>
|
||||||
|
},
|
||||||
string: {
|
string: {
|
||||||
More: '' as IntlString,
|
More: '' as IntlString,
|
||||||
Delete: '' as IntlString,
|
Delete: '' as IntlString,
|
||||||
|
@ -399,6 +399,7 @@ export default plugin(trackerId, {
|
|||||||
Tracker: '' as AnyComponent,
|
Tracker: '' as AnyComponent,
|
||||||
TrackerApp: '' as AnyComponent,
|
TrackerApp: '' as AnyComponent,
|
||||||
RelatedIssues: '' as AnyComponent,
|
RelatedIssues: '' as AnyComponent,
|
||||||
|
RelatedIssuesSection: '' as AnyComponent,
|
||||||
RelatedIssueTemplates: '' as AnyComponent,
|
RelatedIssueTemplates: '' as AnyComponent,
|
||||||
EditIssue: '' as AnyComponent,
|
EditIssue: '' as AnyComponent,
|
||||||
CreateIssue: '' as AnyComponent,
|
CreateIssue: '' as AnyComponent,
|
||||||
|
@ -24,14 +24,16 @@
|
|||||||
let prevKey = key
|
let prevKey = key
|
||||||
let element: HTMLDivElement | undefined
|
let element: HTMLDivElement | undefined
|
||||||
|
|
||||||
let cWidth: number = 0
|
let cWidth: number | undefined = undefined
|
||||||
|
|
||||||
afterUpdate(() => {
|
afterUpdate(() => {
|
||||||
if (prevKey !== key) {
|
if (cWidth !== undefined) {
|
||||||
$fixedWidthStore[prevKey] = 0
|
if (prevKey !== key) {
|
||||||
$fixedWidthStore[key] = 0
|
$fixedWidthStore[prevKey] = 0
|
||||||
prevKey = key
|
$fixedWidthStore[key] = 0
|
||||||
cWidth = 0
|
prevKey = key
|
||||||
|
cWidth = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -43,14 +45,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
$fixedWidthStore[key] = 0
|
if (cWidth === $fixedWidthStore[key]) {
|
||||||
|
// If we are longest element
|
||||||
|
$fixedWidthStore[key] = 0
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
bind:this={element}
|
bind:this={element}
|
||||||
class="flex-no-shrink{addClass ? ` ${addClass}` : ''}"
|
class="flex-no-shrink{addClass ? ` ${addClass}` : ''}"
|
||||||
style="{justify !== '' ? `text-align: ${justify}; ` : ''} min-width: {$fixedWidthStore[key] ?? 0}px;"
|
style:text-align={justify !== '' ? justify : ''}
|
||||||
|
style:min-width={`${$fixedWidthStore[key] ?? 0}}px;`}
|
||||||
use:resizeObserver={resize}
|
use:resizeObserver={resize}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -45,7 +45,10 @@
|
|||||||
selected={viewOptions.groupBy}
|
selected={viewOptions.groupBy}
|
||||||
width="10rem"
|
width="10rem"
|
||||||
justify="left"
|
justify="left"
|
||||||
on:selected={(e) => dispatch('update', { key: 'groupBy', value: e.detail })}
|
on:selected={(e) => {
|
||||||
|
viewOptions.groupBy = e.detail
|
||||||
|
dispatch('update', { key: 'groupBy', value: e.detail })
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="label"><Label label={view.string.Ordering} /></span>
|
<span class="label"><Label label={view.string.Ordering} /></span>
|
||||||
@ -60,6 +63,7 @@
|
|||||||
const key = e.detail
|
const key = e.detail
|
||||||
const value = config.orderBy.find((p) => p[0] === key)
|
const value = config.orderBy.find((p) => p[0] === key)
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
|
viewOptions.orderBy = value
|
||||||
dispatch('update', { key: 'orderBy', value })
|
dispatch('update', { key: 'orderBy', value })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -71,7 +75,10 @@
|
|||||||
{#if isToggleType(model)}
|
{#if isToggleType(model)}
|
||||||
<MiniToggle
|
<MiniToggle
|
||||||
on={viewOptions[model.key]}
|
on={viewOptions[model.key]}
|
||||||
on:change={() => dispatch('update', { key: model.key, value: !viewOptions[model.key] })}
|
on:change={() => {
|
||||||
|
viewOptions[model.key] = !viewOptions[model.key]
|
||||||
|
dispatch('update', { key: model.key, value: viewOptions[model.key] })
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{:else if isDropdownType(model)}
|
{:else if isDropdownType(model)}
|
||||||
{@const items = model.values.filter(({ hidden }) => !hidden?.(viewOptions))}
|
{@const items = model.values.filter(({ hidden }) => !hidden?.(viewOptions))}
|
||||||
@ -81,7 +88,10 @@
|
|||||||
selected={viewOptions[model.key]}
|
selected={viewOptions[model.key]}
|
||||||
width="10rem"
|
width="10rem"
|
||||||
justify="left"
|
justify="left"
|
||||||
on:selected={(e) => dispatch('update', { key: model.key, value: e.detail })}
|
on:selected={(e) => {
|
||||||
|
viewOptions[model.key] = e.detail
|
||||||
|
dispatch('update', { key: model.key, value: e.detail })
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,36 +13,36 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button, eventToHTMLElement, IconDownOutline, Label, showPopup } from '@hcengineering/ui'
|
import { Button, ButtonKind, eventToHTMLElement, IconDownOutline, Label, showPopup } from '@hcengineering/ui'
|
||||||
import { Viewlet, ViewOptionsModel } from '@hcengineering/view'
|
import { Viewlet, ViewOptions } from '@hcengineering/view'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
import view from '../plugin'
|
import view from '../plugin'
|
||||||
import { defaulOptions, getViewOptions, setViewOptions, viewOptionsStore } from '../viewOptions'
|
import { setViewOptions } from '../viewOptions'
|
||||||
import ViewletSetting from './ViewletSetting.svelte'
|
import ViewletSetting from './ViewletSetting.svelte'
|
||||||
import ViewOptions from './ViewOptions.svelte'
|
import ViewOptionsEditor from './ViewOptions.svelte'
|
||||||
|
|
||||||
export let viewlet: Viewlet | undefined
|
export let viewlet: Viewlet | undefined
|
||||||
|
export let kind: ButtonKind = 'secondary'
|
||||||
|
export let viewOptions: ViewOptions
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let btn: HTMLButtonElement
|
let btn: HTMLButtonElement
|
||||||
|
|
||||||
$: viewlet && loadViewOptionsStore(viewlet.viewOptions, viewlet._id)
|
|
||||||
|
|
||||||
function loadViewOptionsStore (config: ViewOptionsModel | undefined, key: string) {
|
|
||||||
if (!config) return
|
|
||||||
viewOptionsStore.set(getViewOptions(key) ?? defaulOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickHandler (event: MouseEvent) {
|
function clickHandler (event: MouseEvent) {
|
||||||
if (viewlet?.viewOptions !== undefined) {
|
if (viewlet?.viewOptions !== undefined) {
|
||||||
showPopup(
|
showPopup(
|
||||||
ViewOptions,
|
ViewOptionsEditor,
|
||||||
{ viewlet, config: viewlet.viewOptions, viewOptions: $viewOptionsStore },
|
{ viewlet, config: viewlet.viewOptions, viewOptions },
|
||||||
eventToHTMLElement(event),
|
eventToHTMLElement(event),
|
||||||
undefined,
|
undefined,
|
||||||
(result) => {
|
(result) => {
|
||||||
if (result?.key === undefined) return
|
if (result?.key === undefined) return
|
||||||
$viewOptionsStore[result.key] = result.value
|
if (viewlet) {
|
||||||
viewOptionsStore.set($viewOptionsStore)
|
viewOptions = { ...viewOptions, [result.key]: result.value }
|
||||||
setViewOptions(viewlet?._id ?? '', $viewOptionsStore)
|
dispatch('viewOptions', viewOptions)
|
||||||
|
setViewOptions(viewlet, viewOptions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -54,7 +54,7 @@
|
|||||||
{#if viewlet}
|
{#if viewlet}
|
||||||
<Button
|
<Button
|
||||||
icon={view.icon.ViewButton}
|
icon={view.icon.ViewButton}
|
||||||
kind={'secondary'}
|
{kind}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
showTooltip={{ label: view.string.CustomizeView }}
|
showTooltip={{ label: view.string.CustomizeView }}
|
||||||
bind:input={btn}
|
bind:input={btn}
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
} from '@hcengineering/view'
|
} from '@hcengineering/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { buildConfigLookup, buildModel, getCategories, getPresenter, groupBy, LoadingProps } from '../../utils'
|
import { buildConfigLookup, buildModel, getCategories, getPresenter, groupBy, LoadingProps } from '../../utils'
|
||||||
import { noCategory, viewOptionsStore } from '../../viewOptions'
|
import { noCategory } from '../../viewOptions'
|
||||||
import ListCategory from './ListCategory.svelte'
|
import ListCategory from './ListCategory.svelte'
|
||||||
|
|
||||||
export let _class: Ref<Class<Doc>>
|
export let _class: Ref<Class<Doc>>
|
||||||
@ -38,33 +38,48 @@
|
|||||||
export let selectedObjectIds: Doc[] = []
|
export let selectedObjectIds: Doc[] = []
|
||||||
export let selectedRowIndex: number | undefined = undefined
|
export let selectedRowIndex: number | undefined = undefined
|
||||||
export let loadingProps: LoadingProps | undefined = undefined
|
export let loadingProps: LoadingProps | undefined = undefined
|
||||||
export let createItemDialog: AnyComponent | undefined
|
export let createItemDialog: AnyComponent | undefined = undefined
|
||||||
export let createItemLabel: IntlString | undefined
|
export let createItemLabel: IntlString | undefined = undefined
|
||||||
export let viewOptions: ViewOptionModel[] | undefined
|
export let viewOptionsConfig: ViewOptionModel[] | undefined
|
||||||
|
export let viewOptions: ViewOptions
|
||||||
|
export let flatHeaders = false
|
||||||
|
export let props: Record<string, any> = {}
|
||||||
|
|
||||||
|
export let documents: Doc[] | undefined = undefined
|
||||||
|
|
||||||
const objectRefs: HTMLElement[] = []
|
const objectRefs: HTMLElement[] = []
|
||||||
let docs: Doc[] = []
|
let docs: Doc[] = []
|
||||||
$: groupByKey = $viewOptionsStore.groupBy ?? noCategory
|
|
||||||
$: orderBy = $viewOptionsStore.orderBy
|
$: groupByKey = viewOptions.groupBy ?? noCategory
|
||||||
|
$: orderBy = viewOptions.orderBy
|
||||||
$: groupedDocs = groupBy(docs, groupByKey)
|
$: groupedDocs = groupBy(docs, groupByKey)
|
||||||
let categories: any[] = []
|
let categories: any[] = []
|
||||||
$: getCategories(client, _class, docs, groupByKey).then((p) => (categories = p))
|
$: getCategories(client, _class, docs, groupByKey).then((p) => {
|
||||||
|
categories = p
|
||||||
|
})
|
||||||
|
|
||||||
const docsQuery = createQuery()
|
const docsQuery = createQuery()
|
||||||
$: resultOptions = { lookup, ...options, sort: { [orderBy[0]]: orderBy[1] } }
|
$: resultOptions = { lookup, ...options, sort: { [orderBy[0]]: orderBy[1] } }
|
||||||
|
|
||||||
let resultQuery: DocumentQuery<Doc> = query
|
let resultQuery: DocumentQuery<Doc> = query
|
||||||
$: getResultQuery(query, viewOptions, $viewOptionsStore).then((p) => (resultQuery = p))
|
$: getResultQuery(query, viewOptionsConfig, viewOptions).then((p) => {
|
||||||
|
resultQuery = { ...p, ...query }
|
||||||
|
})
|
||||||
|
|
||||||
$: docsQuery.query(
|
$: if (documents === undefined) {
|
||||||
_class,
|
docsQuery.query(
|
||||||
resultQuery,
|
_class,
|
||||||
(res) => {
|
resultQuery,
|
||||||
docs = res
|
(res) => {
|
||||||
dispatch('content', docs)
|
docs = res
|
||||||
},
|
dispatch('content', docs)
|
||||||
resultOptions
|
},
|
||||||
)
|
resultOptions
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
docsQuery.unsubscribe()
|
||||||
|
docs = documents
|
||||||
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
@ -117,7 +132,9 @@
|
|||||||
|
|
||||||
let headerComponent: AttributeModel | undefined
|
let headerComponent: AttributeModel | undefined
|
||||||
$: getHeader(_class, groupByKey)
|
$: getHeader(_class, groupByKey)
|
||||||
$: buildModel({ client, _class, keys: config, lookup }).then((res) => (itemModels = res))
|
$: buildModel({ client, _class, keys: config, lookup }).then((res) => {
|
||||||
|
itemModels = res
|
||||||
|
})
|
||||||
|
|
||||||
function getInitIndex (categories: any, i: number): number {
|
function getInitIndex (categories: any, i: number): number {
|
||||||
let res = 0
|
let res = 0
|
||||||
@ -194,6 +211,8 @@
|
|||||||
on:check
|
on:check
|
||||||
on:uncheckAll={uncheckAll}
|
on:uncheckAll={uncheckAll}
|
||||||
on:row-focus
|
on:row-focus
|
||||||
|
{flatHeaders}
|
||||||
|
{props}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,6 +45,8 @@
|
|||||||
export let selectedRowIndex: number | undefined
|
export let selectedRowIndex: number | undefined
|
||||||
export let extraHeaders: AnyComponent[] | undefined
|
export let extraHeaders: AnyComponent[] | undefined
|
||||||
export let objectRefs: HTMLElement[] = []
|
export let objectRefs: HTMLElement[] = []
|
||||||
|
export let flatHeaders = false
|
||||||
|
export let props: Record<string, any> = {}
|
||||||
|
|
||||||
const autoFoldLimit = 20
|
const autoFoldLimit = 20
|
||||||
const defaultLimit = 20
|
const defaultLimit = 20
|
||||||
@ -92,6 +94,8 @@
|
|||||||
{createItemDialog}
|
{createItemDialog}
|
||||||
{createItemLabel}
|
{createItemLabel}
|
||||||
{extraHeaders}
|
{extraHeaders}
|
||||||
|
flat={flatHeaders}
|
||||||
|
{props}
|
||||||
on:more={() => {
|
on:more={() => {
|
||||||
limit += 20
|
limit += 20
|
||||||
}}
|
}}
|
||||||
@ -114,6 +118,7 @@
|
|||||||
on:contextmenu={(event) => handleMenuOpened(event, docObject, initIndex + i)}
|
on:contextmenu={(event) => handleMenuOpened(event, docObject, initIndex + i)}
|
||||||
on:focus={() => {}}
|
on:focus={() => {}}
|
||||||
on:mouseover={() => handleRowFocused(docObject)}
|
on:mouseover={() => handleRowFocused(docObject)}
|
||||||
|
{props}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -41,6 +41,8 @@
|
|||||||
export let limited: number
|
export let limited: number
|
||||||
export let items: Doc[]
|
export let items: Doc[]
|
||||||
export let extraHeaders: AnyComponent[] | undefined
|
export let extraHeaders: AnyComponent[] | undefined
|
||||||
|
export let flat = false
|
||||||
|
export let props: Record<string, any> = {}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
@ -52,7 +54,7 @@
|
|||||||
|
|
||||||
{#if headerComponent || groupByKey === noCategory}
|
{#if headerComponent || groupByKey === noCategory}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="flex-between categoryHeader row" on:click={() => dispatch('collapse')}>
|
<div class="flex-between categoryHeader row" class:flat on:click={() => dispatch('collapse')}>
|
||||||
<div class="flex-row-center gap-2 clear-mins caption-color">
|
<div class="flex-row-center gap-2 clear-mins caption-color">
|
||||||
<FixedColumn key={`list_groupBy_${groupByKey}`} justify={'left'}>
|
<FixedColumn key={`list_groupBy_${groupByKey}`} justify={'left'}>
|
||||||
{#if groupByKey === noCategory}
|
{#if groupByKey === noCategory}
|
||||||
@ -66,7 +68,7 @@
|
|||||||
{#if extraHeaders}
|
{#if extraHeaders}
|
||||||
{#each extraHeaders as extra}
|
{#each extraHeaders as extra}
|
||||||
<FixedColumn key={`list_groupBy_${groupByKey}_extra_${extra}`} justify={'left'}>
|
<FixedColumn key={`list_groupBy_${groupByKey}_extra_${extra}`} justify={'left'}>
|
||||||
<Component is={extra} props={{ value: category, docs: items }} />
|
<Component is={extra} props={{ ...props, value: category, docs: items }} />
|
||||||
</FixedColumn>
|
</FixedColumn>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
@ -109,6 +111,14 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
background: var(--header-bg-color);
|
background: var(--header-bg-color);
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
|
||||||
|
&.flat {
|
||||||
|
background: var(--header-bg-color);
|
||||||
|
background-blend-mode: darken;
|
||||||
|
min-height: 2.25rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
padding: 0 0.25rem 0 0.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.row:not(:last-child) {
|
.row:not(:last-child) {
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
export let groupByKey: string | undefined
|
export let groupByKey: string | undefined
|
||||||
export let checked: boolean
|
export let checked: boolean
|
||||||
export let selected: boolean
|
export let selected: boolean
|
||||||
|
export let props: Record<string, any> = {}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
@ -69,6 +70,7 @@
|
|||||||
<FixedColumn key={`list_item_${attributeModel.key}`} justify={attributeModel.props.fixed}>
|
<FixedColumn key={`list_item_${attributeModel.key}`} justify={attributeModel.props.fixed}>
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={attributeModel.presenter}
|
this={attributeModel.presenter}
|
||||||
|
{...props}
|
||||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||||
object={docObject}
|
object={docObject}
|
||||||
kind={'list'}
|
kind={'list'}
|
||||||
@ -78,6 +80,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={attributeModel.presenter}
|
this={attributeModel.presenter}
|
||||||
|
{...props}
|
||||||
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
value={getObjectValue(attributeModel.key, docObject) ?? ''}
|
||||||
object={docObject}
|
object={docObject}
|
||||||
kind={'list'}
|
kind={'list'}
|
||||||
@ -104,6 +107,7 @@
|
|||||||
{#if attributeModel.props?.optional && attributeModel.props?.excludeByKey !== groupByKey && value !== undefined}
|
{#if attributeModel.props?.optional && attributeModel.props?.excludeByKey !== groupByKey && value !== undefined}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={attributeModel.presenter}
|
this={attributeModel.presenter}
|
||||||
|
{...props}
|
||||||
value={value ?? ''}
|
value={value ?? ''}
|
||||||
objectId={docObject._id}
|
objectId={docObject._id}
|
||||||
groupBy={groupByKey}
|
groupBy={groupByKey}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { Class, Doc, DocumentQuery, FindOptions, Ref, Space } from '@hcengineering/core'
|
import { Class, Doc, DocumentQuery, FindOptions, Ref, Space } from '@hcengineering/core'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
import { AnyComponent, issueSP, Scroller } from '@hcengineering/ui'
|
import { AnyComponent, issueSP, Scroller } from '@hcengineering/ui'
|
||||||
import { BuildModelKey, Viewlet } from '@hcengineering/view'
|
import { BuildModelKey, Viewlet, ViewOptions } from '@hcengineering/view'
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import {
|
import {
|
||||||
ActionContext,
|
ActionContext,
|
||||||
@ -24,6 +24,8 @@
|
|||||||
export let loadingProps: LoadingProps | undefined = undefined
|
export let loadingProps: LoadingProps | undefined = undefined
|
||||||
export let createItemDialog: AnyComponent | undefined
|
export let createItemDialog: AnyComponent | undefined
|
||||||
export let createItemLabel: IntlString | undefined
|
export let createItemLabel: IntlString | undefined
|
||||||
|
export let viewOptions: ViewOptions
|
||||||
|
export let props: Record<string, any> = {}
|
||||||
|
|
||||||
let list: List
|
let list: List
|
||||||
|
|
||||||
@ -57,7 +59,9 @@
|
|||||||
{loadingProps}
|
{loadingProps}
|
||||||
{createItemDialog}
|
{createItemDialog}
|
||||||
{createItemLabel}
|
{createItemLabel}
|
||||||
viewOptions={viewlet.viewOptions?.other}
|
{viewOptions}
|
||||||
|
{props}
|
||||||
|
viewOptionsConfig={viewlet.viewOptions?.other}
|
||||||
selectedObjectIds={$selectionStore ?? []}
|
selectedObjectIds={$selectionStore ?? []}
|
||||||
selectedRowIndex={listProvider.current($focusStore)}
|
selectedRowIndex={listProvider.current($focusStore)}
|
||||||
on:row-focus={(event) => {
|
on:row-focus={(event) => {
|
||||||
|
@ -88,6 +88,8 @@ export { default as FixedColumn } from './components/FixedColumn.svelte'
|
|||||||
export { default as ValueSelector } from './components/ValueSelector.svelte'
|
export { default as ValueSelector } from './components/ValueSelector.svelte'
|
||||||
export { default as ObjectBox } from './components/ObjectBox.svelte'
|
export { default as ObjectBox } from './components/ObjectBox.svelte'
|
||||||
export { default as ObjectPresenter } from './components/ObjectPresenter.svelte'
|
export { default as ObjectPresenter } from './components/ObjectPresenter.svelte'
|
||||||
|
|
||||||
|
export { default as List } from './components/list/List.svelte'
|
||||||
export * from './context'
|
export * from './context'
|
||||||
export * from './filter'
|
export * from './filter'
|
||||||
export * from './selection'
|
export * from './selection'
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { SortingOrder } from '@hcengineering/core'
|
import { SortingOrder } from '@hcengineering/core'
|
||||||
import { getCurrentLocation, locationToUrl } from '@hcengineering/ui'
|
import { getCurrentLocation, locationToUrl } from '@hcengineering/ui'
|
||||||
import { DropdownViewOption, ToggleViewOption, ViewOptionModel, ViewOptions } from '@hcengineering/view'
|
import { DropdownViewOption, ToggleViewOption, Viewlet, ViewOptionModel, ViewOptions } from '@hcengineering/view'
|
||||||
import { writable } from 'svelte/store'
|
|
||||||
|
|
||||||
export const noCategory = '#no_category'
|
export const noCategory = '#no_category'
|
||||||
|
|
||||||
@ -10,8 +9,6 @@ export const defaulOptions: ViewOptions = {
|
|||||||
orderBy: ['modifiedBy', SortingOrder.Descending]
|
orderBy: ['modifiedBy', SortingOrder.Descending]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const viewOptionsStore = writable<ViewOptions>(defaulOptions)
|
|
||||||
|
|
||||||
export function isToggleType (viewOption: ViewOptionModel): viewOption is ToggleViewOption {
|
export function isToggleType (viewOption: ViewOptionModel): viewOption is ToggleViewOption {
|
||||||
return viewOption.type === 'toggle'
|
return viewOption.type === 'toggle'
|
||||||
}
|
}
|
||||||
@ -27,14 +24,27 @@ function makeViewOptionsKey (prefix: string): string {
|
|||||||
return `viewOptions:${prefix}:${locationToUrl(loc)}`
|
return `viewOptions:${prefix}:${locationToUrl(loc)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setViewOptions (prefix: string, options: ViewOptions): void {
|
function _setViewOptions (prefix: string, options: ViewOptions): void {
|
||||||
const key = makeViewOptionsKey(prefix)
|
const key = makeViewOptionsKey(prefix)
|
||||||
localStorage.setItem(key, JSON.stringify(options))
|
localStorage.setItem(key, JSON.stringify(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getViewOptions (prefix: string): ViewOptions | null {
|
export function setViewOptions (viewlet: Viewlet, options: ViewOptions): void {
|
||||||
|
const viewletKey = viewlet?._id + (viewlet?.variant !== undefined ? `-${viewlet.variant}` : '')
|
||||||
|
_setViewOptions(viewletKey, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getViewOptions (prefix: string): ViewOptions | null {
|
||||||
const key = makeViewOptionsKey(prefix)
|
const key = makeViewOptionsKey(prefix)
|
||||||
const options = localStorage.getItem(key)
|
const options = localStorage.getItem(key)
|
||||||
if (options === null) return null
|
if (options === null) return null
|
||||||
return JSON.parse(options)
|
return JSON.parse(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getViewOptions (viewlet: Viewlet | undefined, defaults = defaulOptions): ViewOptions {
|
||||||
|
if (viewlet === undefined) {
|
||||||
|
return { ...defaults }
|
||||||
|
}
|
||||||
|
const viewletKey = viewlet?._id + (viewlet?.variant !== undefined ? `-${viewlet.variant}` : '')
|
||||||
|
return _getViewOptions(viewletKey) ?? defaults
|
||||||
|
}
|
||||||
|
@ -222,6 +222,7 @@ export interface Viewlet extends Doc {
|
|||||||
config: (BuildModelKey | string)[]
|
config: (BuildModelKey | string)[]
|
||||||
hiddenKeys?: string[]
|
hiddenKeys?: string[]
|
||||||
viewOptions?: ViewOptionsModel
|
viewOptions?: ViewOptionsModel
|
||||||
|
variant?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -553,7 +554,8 @@ const view = plugin(viewId, {
|
|||||||
EditDoc: '' as AnyComponent,
|
EditDoc: '' as AnyComponent,
|
||||||
SpacePresenter: '' as AnyComponent,
|
SpacePresenter: '' as AnyComponent,
|
||||||
BooleanTruePresenter: '' as AnyComponent,
|
BooleanTruePresenter: '' as AnyComponent,
|
||||||
ValueSelector: '' as AnyComponent
|
ValueSelector: '' as AnyComponent,
|
||||||
|
GrowPresenter: '' as AnyComponent
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
CustomizeView: '' as IntlString,
|
CustomizeView: '' as IntlString,
|
||||||
|
@ -19,7 +19,12 @@
|
|||||||
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { AnyComponent, Button, IconAdd, SearchEdit, showPanel, showPopup, TabList } from '@hcengineering/ui'
|
import { AnyComponent, Button, IconAdd, SearchEdit, showPanel, showPopup, TabList } from '@hcengineering/ui'
|
||||||
import view, { Viewlet } from '@hcengineering/view'
|
import view, { Viewlet } from '@hcengineering/view'
|
||||||
import { getActiveViewletId, setActiveViewletId, ViewletSettingButton } from '@hcengineering/view-resources'
|
import {
|
||||||
|
getActiveViewletId,
|
||||||
|
getViewOptions,
|
||||||
|
setActiveViewletId,
|
||||||
|
ViewletSettingButton
|
||||||
|
} from '@hcengineering/view-resources'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import plugin from '../plugin'
|
import plugin from '../plugin'
|
||||||
import { classIcon } from '../utils'
|
import { classIcon } from '../utils'
|
||||||
@ -84,6 +89,8 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
$: twoRows = $deviceInfo.twoRows
|
$: twoRows = $deviceInfo.twoRows
|
||||||
|
|
||||||
|
$: viewOptions = getViewOptions(viewlet)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
||||||
@ -128,7 +135,7 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<ViewletSettingButton {viewlet} />
|
<ViewletSettingButton bind:viewOptions {viewlet} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
showPopup
|
showPopup
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import view, { Viewlet, ViewletDescriptor, ViewletPreference } from '@hcengineering/view'
|
import view, { Viewlet, ViewletDescriptor, ViewletPreference } from '@hcengineering/view'
|
||||||
import { FilterBar, FilterButton, ViewletSettingButton } from '@hcengineering/view-resources'
|
import { FilterBar, FilterButton, getViewOptions, ViewletSettingButton } from '@hcengineering/view-resources'
|
||||||
|
|
||||||
export let _class: Ref<Class<Doc>>
|
export let _class: Ref<Class<Doc>>
|
||||||
export let icon: Asset
|
export let icon: Asset
|
||||||
@ -91,6 +91,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: twoRows = $deviceInfo.twoRows
|
$: twoRows = $deviceInfo.twoRows
|
||||||
|
|
||||||
|
$: viewOptions = getViewOptions(viewlet)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
<div class="ac-header withSettings" class:full={!twoRows} class:mini={twoRows}>
|
||||||
@ -107,7 +109,7 @@
|
|||||||
{#if createLabel && createComponent}
|
{#if createLabel && createComponent}
|
||||||
<Button label={createLabel} icon={IconAdd} kind={'primary'} size={'small'} on:click={() => showCreateDialog()} />
|
<Button label={createLabel} icon={IconAdd} kind={'primary'} size={'small'} on:click={() => showCreateDialog()} />
|
||||||
{/if}
|
{/if}
|
||||||
<ViewletSettingButton {viewlet} />
|
<ViewletSettingButton bind:viewOptions {viewlet} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -510,6 +510,7 @@
|
|||||||
/>
|
/>
|
||||||
<div class="workbench-container">
|
<div class="workbench-container">
|
||||||
{#if currentApplication && navigatorModel && navigator && visibileNav}
|
{#if currentApplication && navigatorModel && navigator && visibileNav}
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
{#if visibileNav && navFloat}<div class="cover shown" on:click={() => (visibileNav = false)} />{/if}
|
{#if visibileNav && navFloat}<div class="cover shown" on:click={() => (visibileNav = false)} />{/if}
|
||||||
<div
|
<div
|
||||||
class="antiPanel-navigator {appsDirection === 'horizontal' ? 'portrait' : 'landscape'}"
|
class="antiPanel-navigator {appsDirection === 'horizontal' ? 'portrait' : 'landscape'}"
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user