mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
UBER-413: Allow extensible navigator model (#3477)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
03b9be571b
commit
2869a8e189
@ -149,6 +149,7 @@ export function createModel (builder: Builder): void {
|
|||||||
navigatorModel: {
|
navigatorModel: {
|
||||||
spaces: [
|
spaces: [
|
||||||
{
|
{
|
||||||
|
id: 'boards',
|
||||||
label: board.string.MyBoards,
|
label: board.string.MyBoards,
|
||||||
spaceClass: board.class.Board,
|
spaceClass: board.class.Board,
|
||||||
addSpaceLabel: board.string.BoardCreateLabel,
|
addSpaceLabel: board.string.BoardCreateLabel,
|
||||||
|
@ -421,12 +421,14 @@ export function createModel (builder: Builder, options = { addApplication: true
|
|||||||
],
|
],
|
||||||
spaces: [
|
spaces: [
|
||||||
{
|
{
|
||||||
|
id: 'channels',
|
||||||
label: chunter.string.Channels,
|
label: chunter.string.Channels,
|
||||||
spaceClass: chunter.class.Channel,
|
spaceClass: chunter.class.Channel,
|
||||||
addSpaceLabel: chunter.string.CreateChannel,
|
addSpaceLabel: chunter.string.CreateChannel,
|
||||||
createComponent: chunter.component.CreateChannel
|
createComponent: chunter.component.CreateChannel
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'directMessages',
|
||||||
label: chunter.string.DirectMessages,
|
label: chunter.string.DirectMessages,
|
||||||
spaceClass: chunter.class.DirectMessage,
|
spaceClass: chunter.class.DirectMessage,
|
||||||
addSpaceLabel: chunter.string.NewDirectMessage,
|
addSpaceLabel: chunter.string.NewDirectMessage,
|
||||||
|
@ -163,6 +163,7 @@ export function createModel (builder: Builder): void {
|
|||||||
],
|
],
|
||||||
spaces: [
|
spaces: [
|
||||||
{
|
{
|
||||||
|
id: 'funnels',
|
||||||
label: lead.string.Funnels,
|
label: lead.string.Funnels,
|
||||||
spaceClass: lead.class.Funnel,
|
spaceClass: lead.class.Funnel,
|
||||||
addSpaceLabel: lead.string.CreateFunnel,
|
addSpaceLabel: lead.string.CreateFunnel,
|
||||||
|
@ -543,7 +543,7 @@ export function createModel (builder: Builder): void {
|
|||||||
{
|
{
|
||||||
key: 'assignee',
|
key: 'assignee',
|
||||||
presenter: tracker.component.AssigneeEditor,
|
presenter: tracker.component.AssigneeEditor,
|
||||||
displayProps: { key: 'assigee', fixed: 'right' },
|
displayProps: { key: 'assignee', fixed: 'right' },
|
||||||
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
|
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -1084,6 +1084,7 @@ export function createModel (builder: Builder): void {
|
|||||||
],
|
],
|
||||||
spaces: [
|
spaces: [
|
||||||
{
|
{
|
||||||
|
id: 'projects',
|
||||||
label: tracker.string.Projects,
|
label: tracker.string.Projects,
|
||||||
spaceClass: tracker.class.Project,
|
spaceClass: tracker.class.Project,
|
||||||
addSpaceLabel: tracker.string.CreateProject,
|
addSpaceLabel: tracker.string.CreateProject,
|
||||||
|
@ -19,7 +19,13 @@ import preference, { TPreference } from '@hcengineering/model-preference'
|
|||||||
import { createAction } from '@hcengineering/model-view'
|
import { createAction } from '@hcengineering/model-view'
|
||||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||||
import view, { KeyBinding } from '@hcengineering/view'
|
import view, { KeyBinding } from '@hcengineering/view'
|
||||||
import type { Application, HiddenApplication, SpaceView, ViewConfiguration } from '@hcengineering/workbench'
|
import type {
|
||||||
|
Application,
|
||||||
|
ApplicationNavModel,
|
||||||
|
HiddenApplication,
|
||||||
|
SpaceView,
|
||||||
|
ViewConfiguration
|
||||||
|
} from '@hcengineering/workbench'
|
||||||
|
|
||||||
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
import core, { TClass, TDoc } from '@hcengineering/model-core'
|
||||||
import workbench from './plugin'
|
import workbench from './plugin'
|
||||||
@ -37,6 +43,12 @@ export class TApplication extends TDoc implements Application {
|
|||||||
hidden!: boolean
|
hidden!: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Model(workbench.class.ApplicationNavModel, core.class.Doc, DOMAIN_MODEL)
|
||||||
|
@UX(workbench.string.Application)
|
||||||
|
export class TApplicationNavModel extends TDoc implements ApplicationNavModel {
|
||||||
|
extends!: Ref<Application>
|
||||||
|
}
|
||||||
|
|
||||||
@Model(workbench.class.HiddenApplication, preference.class.Preference)
|
@Model(workbench.class.HiddenApplication, preference.class.Preference)
|
||||||
export class THiddenApplication extends TPreference implements HiddenApplication {
|
export class THiddenApplication extends TPreference implements HiddenApplication {
|
||||||
@Prop(TypeRef(workbench.class.Application), workbench.string.HiddenApplication)
|
@Prop(TypeRef(workbench.class.Application), workbench.string.HiddenApplication)
|
||||||
@ -49,7 +61,7 @@ export class TSpaceView extends TClass implements SpaceView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createModel(TApplication, TSpaceView, THiddenApplication)
|
builder.createModel(TApplication, TSpaceView, THiddenApplication, TApplicationNavModel)
|
||||||
builder.mixin(workbench.class.Application, core.class.Class, view.mixin.ObjectPresenter, {
|
builder.mixin(workbench.class.Application, core.class.Class, view.mixin.ObjectPresenter, {
|
||||||
presenter: workbench.component.ApplicationPresenter
|
presenter: workbench.component.ApplicationPresenter
|
||||||
})
|
})
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { deepEqual } from 'fast-equals'
|
||||||
import { DocumentUpdate, Hierarchy, MixinData, MixinUpdate, ModelDb, toFindResult } from '.'
|
import { DocumentUpdate, Hierarchy, MixinData, MixinUpdate, ModelDb, toFindResult } from '.'
|
||||||
import type {
|
import type {
|
||||||
Account,
|
Account,
|
||||||
@ -15,7 +16,7 @@ import type {
|
|||||||
import { Client } from './client'
|
import { Client } from './client'
|
||||||
import core from './component'
|
import core from './component'
|
||||||
import type { DocumentQuery, FindOptions, FindResult, TxResult, WithLookup } from './storage'
|
import type { DocumentQuery, FindOptions, FindResult, TxResult, WithLookup } from './storage'
|
||||||
import { DocumentClassQuery, Tx, TxCUD, TxFactory } from './tx'
|
import { DocumentClassQuery, Tx, TxCUD, TxFactory, TxProcessor } from './tx'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -269,6 +270,57 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
|||||||
apply (scope: string): ApplyOperations {
|
apply (scope: string): ApplyOperations {
|
||||||
return new ApplyOperations(this, scope)
|
return new ApplyOperations(this, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async diffUpdate (doc: Doc, raw: Doc | Data<Doc>, date: Timestamp): Promise<Doc> {
|
||||||
|
// We need to update fields if they are different.
|
||||||
|
const documentUpdate: DocumentUpdate<Doc> = {}
|
||||||
|
for (const [k, v] of Object.entries(raw)) {
|
||||||
|
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space', 'attachedTo', 'attachedToClass'].includes(k)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const dv = (doc as any)[k]
|
||||||
|
if (!deepEqual(dv, v) && v != null) {
|
||||||
|
;(documentUpdate as any)[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(documentUpdate).length > 0) {
|
||||||
|
await this.update(doc, documentUpdate, false, date, doc.modifiedBy)
|
||||||
|
TxProcessor.applyUpdate(doc, documentUpdate)
|
||||||
|
}
|
||||||
|
return doc
|
||||||
|
}
|
||||||
|
|
||||||
|
async mixinDiffUpdate (
|
||||||
|
doc: Doc,
|
||||||
|
raw: Doc | Data<Doc>,
|
||||||
|
mixin: Ref<Class<Mixin<Doc>>>,
|
||||||
|
modifiedBy: Ref<Account>,
|
||||||
|
modifiedOn: Timestamp
|
||||||
|
): Promise<Doc> {
|
||||||
|
// We need to update fields if they are different.
|
||||||
|
|
||||||
|
if (!this.getHierarchy().hasMixin(doc, mixin)) {
|
||||||
|
await this.createMixin(doc._id, doc._class, doc.space, mixin, raw as MixinData<Doc, Doc>, modifiedOn, modifiedBy)
|
||||||
|
TxProcessor.applyUpdate(this.getHierarchy().as(doc, mixin), raw)
|
||||||
|
return doc
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentUpdate: MixinUpdate<Doc, Doc> = {}
|
||||||
|
for (const [k, v] of Object.entries(raw)) {
|
||||||
|
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space', 'attachedTo', 'attachedToClass'].includes(k)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const dv = (doc as any)[k]
|
||||||
|
if (!deepEqual(dv, v) && v != null) {
|
||||||
|
;(documentUpdate as any)[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(documentUpdate).length > 0) {
|
||||||
|
await this.updateMixin(doc._id, doc._class, doc.space, mixin, documentUpdate, modifiedOn, modifiedBy)
|
||||||
|
TxProcessor.applyUpdate(this.getHierarchy().as(doc, mixin), documentUpdate)
|
||||||
|
}
|
||||||
|
return doc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
import view, { Viewlet } from '@hcengineering/view'
|
import view, { Viewlet } from '@hcengineering/view'
|
||||||
import {
|
import {
|
||||||
FilterBar,
|
FilterBar,
|
||||||
|
SpaceHeader,
|
||||||
|
ViewletContentView,
|
||||||
ViewletSettingButton,
|
ViewletSettingButton,
|
||||||
activeViewlet,
|
activeViewlet,
|
||||||
getViewOptions,
|
getViewOptions,
|
||||||
@ -23,8 +25,7 @@
|
|||||||
} from '@hcengineering/view-resources'
|
} from '@hcengineering/view-resources'
|
||||||
import { onDestroy } from 'svelte'
|
import { onDestroy } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
import IssuesContent from './IssuesContent.svelte'
|
import CreateIssue from '../CreateIssue.svelte'
|
||||||
import IssuesHeader from './IssuesHeader.svelte'
|
|
||||||
|
|
||||||
export let space: Ref<Space> | undefined = undefined
|
export let space: Ref<Space> | undefined = undefined
|
||||||
export let query: DocumentQuery<Issue> = {}
|
export let query: DocumentQuery<Issue> = {}
|
||||||
@ -88,7 +89,8 @@
|
|||||||
$: viewOptions = getViewOptions(viewlet, $viewOptionStore)
|
$: viewOptions = getViewOptions(viewlet, $viewOptionStore)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IssuesHeader
|
<SpaceHeader
|
||||||
|
_class={tracker.class.Issue}
|
||||||
bind:viewlet
|
bind:viewlet
|
||||||
bind:search
|
bind:search
|
||||||
showLabelSelector={$$slots.label_selector}
|
showLabelSelector={$$slots.label_selector}
|
||||||
@ -117,12 +119,21 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</IssuesHeader>
|
</SpaceHeader>
|
||||||
<FilterBar _class={tracker.class.Issue} query={searchQuery} {viewOptions} on:change={(e) => (resultQuery = e.detail)} />
|
<FilterBar _class={tracker.class.Issue} query={searchQuery} {viewOptions} on:change={(e) => (resultQuery = e.detail)} />
|
||||||
<slot name="afterHeader" />
|
<slot name="afterHeader" />
|
||||||
<div class="popupPanel rowContent">
|
<div class="popupPanel rowContent">
|
||||||
{#if viewlet}
|
{#if viewlet}
|
||||||
<IssuesContent {viewlet} query={resultQuery} {space} {viewOptions} />
|
<ViewletContentView
|
||||||
|
_class={tracker.class.Issue}
|
||||||
|
{viewlet}
|
||||||
|
query={resultQuery}
|
||||||
|
{space}
|
||||||
|
{viewOptions}
|
||||||
|
createItemDialog={CreateIssue}
|
||||||
|
createItemLabel={tracker.string.AddIssueTooltip}
|
||||||
|
createItemDialogProps={{ shouldSaveDraft: true }}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $$slots.aside !== undefined && asideShown}
|
{#if $$slots.aside !== undefined && asideShown}
|
||||||
<div class="popupPanel-body__aside" class:shown={asideShown}>
|
<div class="popupPanel-body__aside" class:shown={asideShown}>
|
||||||
|
@ -23,9 +23,10 @@
|
|||||||
themeStore
|
themeStore
|
||||||
} from '@hcengineering/ui'
|
} from '@hcengineering/ui'
|
||||||
import { NavLink, TreeNode } from '@hcengineering/view-resources'
|
import { NavLink, TreeNode } from '@hcengineering/view-resources'
|
||||||
import { SpacesNavModel } from '@hcengineering/workbench'
|
import { SpacesNavModel, SpecialNavModel } from '@hcengineering/workbench'
|
||||||
import { SpecialElement } from '@hcengineering/workbench-resources'
|
import { SpecialElement } from '@hcengineering/workbench-resources'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
|
import { getResource } from '@hcengineering/platform'
|
||||||
|
|
||||||
export let space: Project
|
export let space: Project
|
||||||
export let model: SpacesNavModel
|
export let model: SpacesNavModel
|
||||||
@ -38,9 +39,30 @@
|
|||||||
const getSpaceCollapsedKey = () => `${getCurrentLocation().path[1]}_${space._id}_collapsed`
|
const getSpaceCollapsedKey = () => `${getCurrentLocation().path[1]}_${space._id}_collapsed`
|
||||||
|
|
||||||
$: collapsed = localStorage.getItem(getSpaceCollapsedKey()) === COLLAPSED
|
$: collapsed = localStorage.getItem(getSpaceCollapsedKey()) === COLLAPSED
|
||||||
|
|
||||||
|
let specials: SpecialNavModel[] = []
|
||||||
|
|
||||||
|
async function updateSpecials (model: SpacesNavModel, space: Project): Promise<void> {
|
||||||
|
const newSpecials: SpecialNavModel[] = []
|
||||||
|
for (const sp of model.specials ?? []) {
|
||||||
|
let shouldAdd = true
|
||||||
|
if (sp.visibleIf !== undefined) {
|
||||||
|
const visibleIf = await getResource(sp.visibleIf)
|
||||||
|
if (visibleIf !== undefined) {
|
||||||
|
shouldAdd = await visibleIf([space])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldAdd) {
|
||||||
|
newSpecials.push(sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
specials = newSpecials
|
||||||
|
}
|
||||||
|
|
||||||
|
$: updateSpecials(model, space)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if model.specials}
|
{#if specials}
|
||||||
<TreeNode
|
<TreeNode
|
||||||
{collapsed}
|
{collapsed}
|
||||||
icon={space?.icon === tracker.component.IconWithEmoji ? IconWithEmoji : space?.icon ?? model.icon}
|
icon={space?.icon === tracker.component.IconWithEmoji ? IconWithEmoji : space?.icon ?? model.icon}
|
||||||
@ -56,7 +78,7 @@
|
|||||||
actions={() => getActions(space)}
|
actions={() => getActions(space)}
|
||||||
on:click={() => localStorage.setItem(getSpaceCollapsedKey(), collapsed ? '' : COLLAPSED)}
|
on:click={() => localStorage.setItem(getSpaceCollapsedKey(), collapsed ? '' : COLLAPSED)}
|
||||||
>
|
>
|
||||||
{#each model.specials as special}
|
{#each specials as special}
|
||||||
<NavLink space={space._id} special={special.id}>
|
<NavLink space={space._id} special={special.id}>
|
||||||
<SpecialElement
|
<SpecialElement
|
||||||
indent={'ml-2'}
|
indent={'ml-2'}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
import view, { Viewlet } from '@hcengineering/view'
|
import view, { Viewlet } from '@hcengineering/view'
|
||||||
import {
|
import {
|
||||||
FilterBar,
|
FilterBar,
|
||||||
|
SpaceHeader,
|
||||||
ViewletSettingButton,
|
ViewletSettingButton,
|
||||||
activeViewlet,
|
activeViewlet,
|
||||||
getViewOptions,
|
getViewOptions,
|
||||||
@ -24,7 +25,6 @@
|
|||||||
} from '@hcengineering/view-resources'
|
} from '@hcengineering/view-resources'
|
||||||
import { onDestroy } from 'svelte'
|
import { onDestroy } from 'svelte'
|
||||||
import tracker from '../../plugin'
|
import tracker from '../../plugin'
|
||||||
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'
|
||||||
|
|
||||||
@ -91,7 +91,15 @@
|
|||||||
$: viewOptions = getViewOptions(viewlet, $viewOptionStore)
|
$: viewOptions = getViewOptions(viewlet, $viewOptionStore)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<IssuesHeader {space} {viewlets} {label} bind:viewlet bind:search showLabelSelector={$$slots.label_selector}>
|
<SpaceHeader
|
||||||
|
_class={tracker.class.IssueTemplate}
|
||||||
|
{space}
|
||||||
|
{viewlets}
|
||||||
|
{label}
|
||||||
|
bind:viewlet
|
||||||
|
bind:search
|
||||||
|
showLabelSelector={$$slots.label_selector}
|
||||||
|
>
|
||||||
<svelte:fragment slot="label_selector">
|
<svelte:fragment slot="label_selector">
|
||||||
<slot name="label_selector" />
|
<slot name="label_selector" />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
@ -115,7 +123,7 @@
|
|||||||
<ViewletSettingButton bind:viewOptions {viewlet} />
|
<ViewletSettingButton bind:viewOptions {viewlet} />
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</IssuesHeader>
|
</SpaceHeader>
|
||||||
<slot name="afterHeader" />
|
<slot name="afterHeader" />
|
||||||
<FilterBar
|
<FilterBar
|
||||||
_class={tracker.class.IssueTemplate}
|
_class={tracker.class.IssueTemplate}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, Space } from '@hcengineering/core'
|
import { Class, Doc, Ref, Space } from '@hcengineering/core'
|
||||||
import { TabList, SearchEdit, IModeSelector, ModeSelector } from '@hcengineering/ui'
|
import { TabList, SearchEdit, IModeSelector, ModeSelector } from '@hcengineering/ui'
|
||||||
import { Viewlet } from '@hcengineering/view'
|
import { Viewlet } from '@hcengineering/view'
|
||||||
import { FilterButton, setActiveViewletId } from '@hcengineering/view-resources'
|
|
||||||
import tracker from '../../plugin'
|
|
||||||
import { WithLookup } from '@hcengineering/core'
|
import { WithLookup } from '@hcengineering/core'
|
||||||
|
import { setActiveViewletId } from '../utils'
|
||||||
|
import FilterButton from './filter/FilterButton.svelte'
|
||||||
|
|
||||||
export let space: Ref<Space> | undefined = undefined
|
export let space: Ref<Space> | undefined = undefined
|
||||||
|
export let _class: Ref<Class<Doc>>
|
||||||
export let viewlet: WithLookup<Viewlet> | undefined
|
export let viewlet: WithLookup<Viewlet> | undefined
|
||||||
export let viewlets: WithLookup<Viewlet>[] = []
|
export let viewlets: WithLookup<Viewlet>[] = []
|
||||||
export let label: string
|
export let label: string
|
||||||
@ -65,7 +66,7 @@
|
|||||||
<SearchEdit bind:value={search} on:change={() => {}} />
|
<SearchEdit bind:value={search} on:change={() => {}} />
|
||||||
<!-- <ActionIcon icon={IconMoreH} size={'small'} /> -->
|
<!-- <ActionIcon icon={IconMoreH} size={'small'} /> -->
|
||||||
<div class="buttons-divider" />
|
<div class="buttons-divider" />
|
||||||
<FilterButton _class={tracker.class.Issue} {space} />
|
<FilterButton {_class} {space} />
|
||||||
</div>
|
</div>
|
||||||
<div class="ac-header-full medium-gap">
|
<div class="ac-header-full medium-gap">
|
||||||
<slot name="extra" />
|
<slot name="extra" />
|
@ -1,18 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
import { Class, Doc, DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||||
import { Issue } from '@hcengineering/tracker'
|
import { AnySvelteComponent, Component, Loading } from '@hcengineering/ui'
|
||||||
import { Component, Loading } from '@hcengineering/ui'
|
|
||||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||||
import tracker from '../../plugin'
|
|
||||||
import CreateIssue from '../CreateIssue.svelte'
|
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
|
import { IntlString } from '@hcengineering/platform'
|
||||||
|
|
||||||
export let viewlet: WithLookup<Viewlet>
|
export let viewlet: WithLookup<Viewlet>
|
||||||
export let query: DocumentQuery<Issue> = {}
|
export let _class: Ref<Class<Doc>>
|
||||||
|
export let query: DocumentQuery<Doc> = {}
|
||||||
export let space: Ref<Space> | undefined
|
export let space: Ref<Space> | undefined
|
||||||
|
|
||||||
export let viewOptions: ViewOptions
|
export let viewOptions: ViewOptions
|
||||||
|
|
||||||
|
export let createItemDialog: AnySvelteComponent | undefined = undefined
|
||||||
|
export let createItemLabel: IntlString | undefined = undefined
|
||||||
|
export let createItemDialogProps = { shouldSaveDraft: true }
|
||||||
|
|
||||||
const preferenceQuery = createQuery()
|
const preferenceQuery = createQuery()
|
||||||
let preference: ViewletPreference | undefined
|
let preference: ViewletPreference | undefined
|
||||||
let loading = true
|
let loading = true
|
||||||
@ -29,10 +32,6 @@
|
|||||||
},
|
},
|
||||||
{ limit: 1 }
|
{ limit: 1 }
|
||||||
)
|
)
|
||||||
|
|
||||||
const createItemDialog = CreateIssue
|
|
||||||
const createItemLabel = tracker.string.AddIssueTooltip
|
|
||||||
const createItemDialogProps = { shouldSaveDraft: true }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if viewlet?.$lookup?.descriptor?.component}
|
{#if viewlet?.$lookup?.descriptor?.component}
|
||||||
@ -42,7 +41,7 @@
|
|||||||
<Component
|
<Component
|
||||||
is={viewlet.$lookup.descriptor.component}
|
is={viewlet.$lookup.descriptor.component}
|
||||||
props={{
|
props={{
|
||||||
_class: tracker.class.Issue,
|
_class,
|
||||||
config: preference?.config ?? viewlet.config,
|
config: preference?.config ?? viewlet.config,
|
||||||
options: viewlet.options,
|
options: viewlet.options,
|
||||||
createItemDialog,
|
createItemDialog,
|
@ -77,6 +77,8 @@ import ValueSelector from './components/ValueSelector.svelte'
|
|||||||
import ViewletSettingButton from './components/ViewletSettingButton.svelte'
|
import ViewletSettingButton from './components/ViewletSettingButton.svelte'
|
||||||
import DateFilterPresenter from './components/filter/DateFilterPresenter.svelte'
|
import DateFilterPresenter from './components/filter/DateFilterPresenter.svelte'
|
||||||
import ArrayFilter from './components/filter/ArrayFilter.svelte'
|
import ArrayFilter from './components/filter/ArrayFilter.svelte'
|
||||||
|
import SpaceHeader from './components/SpaceHeader.svelte'
|
||||||
|
import ViewletContentView from './components/ViewletContentView.svelte'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
afterResult,
|
afterResult,
|
||||||
@ -175,7 +177,9 @@ export {
|
|||||||
DocNavLink,
|
DocNavLink,
|
||||||
EnumEditor,
|
EnumEditor,
|
||||||
StringPresenter,
|
StringPresenter,
|
||||||
EditBoxPopup
|
EditBoxPopup,
|
||||||
|
SpaceHeader,
|
||||||
|
ViewletContentView
|
||||||
}
|
}
|
||||||
|
|
||||||
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
|
function PositionElementAlignment (e?: Event): PopupAlignment | undefined {
|
||||||
|
@ -63,7 +63,7 @@
|
|||||||
import { getContext, onDestroy, onMount, tick } from 'svelte'
|
import { getContext, onDestroy, onMount, tick } from 'svelte'
|
||||||
import { subscribeMobile } from '../mobile'
|
import { subscribeMobile } from '../mobile'
|
||||||
import workbench from '../plugin'
|
import workbench from '../plugin'
|
||||||
import { workspacesStore } from '../utils'
|
import { buildNavModel, workspacesStore } from '../utils'
|
||||||
import AccountPopup from './AccountPopup.svelte'
|
import AccountPopup from './AccountPopup.svelte'
|
||||||
import AppItem from './AppItem.svelte'
|
import AppItem from './AppItem.svelte'
|
||||||
import AppSwitcher from './AppSwitcher.svelte'
|
import AppSwitcher from './AppSwitcher.svelte'
|
||||||
@ -326,7 +326,7 @@
|
|||||||
clear(1)
|
clear(1)
|
||||||
currentAppAlias = app
|
currentAppAlias = app
|
||||||
currentApplication = await client.findOne(workbench.class.Application, { alias: app })
|
currentApplication = await client.findOne(workbench.class.Application, { alias: app })
|
||||||
navigatorModel = currentApplication?.navigatorModel
|
navigatorModel = await buildNavModel(client, currentApplication)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -17,11 +17,12 @@
|
|||||||
import core from '@hcengineering/core'
|
import core from '@hcengineering/core'
|
||||||
import { DocUpdates } from '@hcengineering/notification'
|
import { DocUpdates } from '@hcengineering/notification'
|
||||||
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
import { NotificationClientImpl } from '@hcengineering/notification-resources'
|
||||||
import { getResource } from '@hcengineering/platform'
|
import { IntlString, getResource } from '@hcengineering/platform'
|
||||||
import preference from '@hcengineering/preference'
|
import preference from '@hcengineering/preference'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
|
AnyComponent,
|
||||||
AnySvelteComponent,
|
AnySvelteComponent,
|
||||||
IconAdd,
|
IconAdd,
|
||||||
IconEdit,
|
IconEdit,
|
||||||
@ -49,14 +50,14 @@
|
|||||||
const client = getClient()
|
const client = getClient()
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const addSpace: Action = {
|
const addSpace = (addSpaceLabel: IntlString, createComponent: AnyComponent): Action => ({
|
||||||
label: model.addSpaceLabel,
|
label: addSpaceLabel,
|
||||||
icon: IconAdd,
|
icon: IconAdd,
|
||||||
action: async (_id: Ref<Doc>): Promise<void> => {
|
action: async (_id: Ref<Doc>): Promise<void> => {
|
||||||
dispatch('open')
|
dispatch('open')
|
||||||
showPopup(model.createComponent, {}, 'top')
|
showPopup(createComponent, {}, 'top')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const browseSpaces: Action = {
|
const browseSpaces: Action = {
|
||||||
label: plugin.string.BrowseSpaces,
|
label: plugin.string.BrowseSpaces,
|
||||||
@ -108,7 +109,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getParentActions (): Action[] {
|
function getParentActions (): Action[] {
|
||||||
return hasSpaceBrowser ? [browseSpaces, addSpace] : [addSpace]
|
const result = hasSpaceBrowser ? [browseSpaces] : []
|
||||||
|
if (model.addSpaceLabel !== undefined && model.createComponent !== undefined) {
|
||||||
|
result.push(addSpace(model.addSpaceLabel, model.createComponent))
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPresenter (_class: Ref<Class<Doc>>): Promise<AnySvelteComponent | undefined> {
|
async function getPresenter (_class: Ref<Class<Doc>>): Promise<AnySvelteComponent | undefined> {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Class, Client, Doc, Obj, Ref, Space } from '@hcengineering/core'
|
import type { Class, Client, Doc, Obj, Ref, Space, TxOperations } from '@hcengineering/core'
|
||||||
import core from '@hcengineering/core'
|
import core from '@hcengineering/core'
|
||||||
import type { Workspace } from '@hcengineering/login'
|
import type { Workspace } from '@hcengineering/login'
|
||||||
import type { Asset } from '@hcengineering/platform'
|
import type { Asset } from '@hcengineering/platform'
|
||||||
@ -146,3 +146,38 @@ export async function showApplication (app: Application): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const workspacesStore = writable<Workspace[]>([])
|
export const workspacesStore = writable<Workspace[]>([])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function buildNavModel (
|
||||||
|
client: TxOperations,
|
||||||
|
currentApplication?: Application
|
||||||
|
): Promise<NavigatorModel | undefined> {
|
||||||
|
let newNavModel = currentApplication?.navigatorModel
|
||||||
|
if (currentApplication !== undefined) {
|
||||||
|
const models = await client.findAll(workbench.class.ApplicationNavModel, { extends: currentApplication._id })
|
||||||
|
for (const nm of models) {
|
||||||
|
const spaces = newNavModel?.spaces ?? []
|
||||||
|
// Check for extending
|
||||||
|
for (const sp of spaces) {
|
||||||
|
const extend = (nm.spaces ?? []).find((p) => p.id === sp.id)
|
||||||
|
if (extend !== undefined) {
|
||||||
|
sp.label = sp.label ?? extend.label
|
||||||
|
sp.createComponent = sp.createComponent ?? extend.createComponent
|
||||||
|
sp.addSpaceLabel = sp.addSpaceLabel ?? extend.addSpaceLabel
|
||||||
|
sp.icon = sp.icon ?? extend.icon
|
||||||
|
sp.visibleIf = sp.visibleIf ?? extend.visibleIf
|
||||||
|
sp.specials = [...(sp.specials ?? []), ...(extend.specials ?? [])]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newSpaces = (nm.spaces ?? []).filter((it) => !spaces.some((sp) => sp.id === it.id))
|
||||||
|
newNavModel = {
|
||||||
|
spaces: [...spaces, ...newSpaces],
|
||||||
|
specials: [...(newNavModel?.specials ?? []), ...(nm.specials ?? [])],
|
||||||
|
aside: newNavModel?.aside ?? nm?.aside
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newNavModel
|
||||||
|
}
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
import type { Class, Doc, Mixin, Obj, Ref, Space } from '@hcengineering/core'
|
import type { Class, Doc, Mixin, Obj, Ref, Space } from '@hcengineering/core'
|
||||||
import type { Asset, IntlString, Metadata, Plugin, Resource } from '@hcengineering/platform'
|
import type { Asset, IntlString, Metadata, Plugin, Resource } from '@hcengineering/platform'
|
||||||
import { plugin } from '@hcengineering/platform'
|
import { plugin } from '@hcengineering/platform'
|
||||||
|
import type { Preference } from '@hcengineering/preference'
|
||||||
import { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui'
|
import { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui'
|
||||||
import { ViewAction } from '@hcengineering/view'
|
import { ViewAction } from '@hcengineering/view'
|
||||||
import type { Preference } from '@hcengineering/preference'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -28,7 +28,10 @@ export interface Application extends Doc {
|
|||||||
alias: string
|
alias: string
|
||||||
icon: Asset
|
icon: Asset
|
||||||
hidden: boolean
|
hidden: boolean
|
||||||
|
|
||||||
|
// Also attached ApplicationNavModel will be joined after this one main.
|
||||||
navigatorModel?: NavigatorModel
|
navigatorModel?: NavigatorModel
|
||||||
|
|
||||||
locationResolver?: Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
|
locationResolver?: Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
|
||||||
|
|
||||||
// Component will be displayed in case navigator model is not defined, or nothing is selected in navigator model
|
// Component will be displayed in case navigator model is not defined, or nothing is selected in navigator model
|
||||||
@ -40,6 +43,17 @@ export interface Application extends Doc {
|
|||||||
navFooterComponent?: AnyComponent
|
navFooterComponent?: AnyComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface ApplicationNavModel extends Doc {
|
||||||
|
extends: Ref<Application>
|
||||||
|
|
||||||
|
spaces?: SpacesNavModel[]
|
||||||
|
specials?: SpecialNavModel[]
|
||||||
|
aside?: AnyComponent
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -51,10 +65,11 @@ export interface HiddenApplication extends Preference {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface SpacesNavModel {
|
export interface SpacesNavModel {
|
||||||
label: IntlString
|
id: string // Id could be used for extending of navigation model
|
||||||
|
label?: IntlString
|
||||||
spaceClass: Ref<Class<Space>>
|
spaceClass: Ref<Class<Space>>
|
||||||
addSpaceLabel: IntlString
|
addSpaceLabel?: IntlString
|
||||||
createComponent: AnyComponent
|
createComponent?: AnyComponent
|
||||||
icon?: Asset
|
icon?: Asset
|
||||||
|
|
||||||
// Child special items.
|
// Child special items.
|
||||||
@ -118,6 +133,7 @@ export const workbenchId = 'workbench' as Plugin
|
|||||||
export default plugin(workbenchId, {
|
export default plugin(workbenchId, {
|
||||||
class: {
|
class: {
|
||||||
Application: '' as Ref<Class<Application>>,
|
Application: '' as Ref<Class<Application>>,
|
||||||
|
ApplicationNavModel: '' as Ref<Class<ApplicationNavModel>>,
|
||||||
HiddenApplication: '' as Ref<Class<HiddenApplication>>
|
HiddenApplication: '' as Ref<Class<HiddenApplication>>
|
||||||
},
|
},
|
||||||
mixin: {
|
mixin: {
|
||||||
|
Loading…
Reference in New Issue
Block a user