mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-30 20:28:20 +00:00
Members init (#1479)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
c1a697ee5e
commit
ed0a747330
@ -14,11 +14,31 @@
|
||||
//
|
||||
|
||||
import activity from '@anticrm/activity'
|
||||
import type { Backlink, Channel, ChunterMessage, Comment, Message, SavedMessages, ThreadMessage } from '@anticrm/chunter'
|
||||
import type {
|
||||
Backlink,
|
||||
Channel,
|
||||
ChunterMessage,
|
||||
Comment,
|
||||
Message,
|
||||
SavedMessages,
|
||||
ThreadMessage
|
||||
} from '@anticrm/chunter'
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import type { Account, Class, Doc, Domain, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import { IndexKind } from '@anticrm/core'
|
||||
import { ArrOf, Builder, Collection, Index, Model, Prop, TypeMarkup, TypeRef, TypeString, TypeTimestamp, UX } from '@anticrm/model'
|
||||
import {
|
||||
ArrOf,
|
||||
Builder,
|
||||
Collection,
|
||||
Index,
|
||||
Model,
|
||||
Prop,
|
||||
TypeMarkup,
|
||||
TypeRef,
|
||||
TypeString,
|
||||
TypeTimestamp,
|
||||
UX
|
||||
} from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import core, { TAttachedDoc, TSpace } from '@anticrm/model-core'
|
||||
import view from '@anticrm/model-view'
|
||||
@ -131,11 +151,16 @@ export function createModel (builder: Builder): void {
|
||||
header: chunter.component.ChannelHeader
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.ViewletDescriptor, core.space.Model, {
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
label: chunter.string.Chat,
|
||||
icon: view.icon.Table,
|
||||
component: chunter.component.ChannelView
|
||||
}, chunter.viewlet.Chat)
|
||||
},
|
||||
chunter.viewlet.Chat
|
||||
)
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: chunter.class.Message,
|
||||
@ -225,12 +250,29 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(workbench.class.Application, core.space.Model, {
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
core.space.Model,
|
||||
{
|
||||
label: chunter.string.ApplicationLabelChunter,
|
||||
icon: chunter.icon.Chunter,
|
||||
hidden: false,
|
||||
navigatorModel: {
|
||||
specials: [
|
||||
{
|
||||
id: 'spaceBrowser',
|
||||
component: workbench.component.SpaceBrowser,
|
||||
icon: workbench.icon.Search,
|
||||
label: chunter.string.ChannelBrowser,
|
||||
position: 'top',
|
||||
spaceClass: chunter.class.Channel,
|
||||
componentProps: {
|
||||
_class: chunter.class.Channel,
|
||||
label: chunter.string.ChannelBrowser,
|
||||
createItemDialog: chunter.component.CreateChannel,
|
||||
createItemLabel: chunter.string.CreateChannel
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'archive',
|
||||
component: workbench.component.Archive,
|
||||
@ -264,13 +306,18 @@ export function createModel (builder: Builder): void {
|
||||
],
|
||||
aside: chunter.component.ThreadView
|
||||
}
|
||||
}, chunter.app.Chunter)
|
||||
},
|
||||
chunter.app.Chunter
|
||||
)
|
||||
|
||||
builder.mixin(chunter.class.Comment, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: chunter.component.CommentPresenter
|
||||
})
|
||||
|
||||
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: chunter.class.Comment,
|
||||
icon: chunter.icon.Chunter,
|
||||
txClass: core.class.TxCreateDoc,
|
||||
@ -279,18 +326,28 @@ export function createModel (builder: Builder): void {
|
||||
display: 'content',
|
||||
editable: true,
|
||||
hideOnRemove: true
|
||||
}, chunter.ids.TxCommentCreate)
|
||||
},
|
||||
chunter.ids.TxCommentCreate
|
||||
)
|
||||
|
||||
// We need to define this one, to hide default attached object removed case
|
||||
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: chunter.class.Comment,
|
||||
icon: chunter.icon.Chunter,
|
||||
txClass: core.class.TxRemoveDoc,
|
||||
display: 'inline',
|
||||
hideOnRemove: true
|
||||
}, chunter.ids.TxCommentRemove)
|
||||
},
|
||||
chunter.ids.TxCommentRemove
|
||||
)
|
||||
|
||||
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: chunter.class.Backlink,
|
||||
icon: chunter.icon.Chunter,
|
||||
txClass: core.class.TxCreateDoc,
|
||||
@ -300,16 +357,23 @@ export function createModel (builder: Builder): void {
|
||||
display: 'emphasized',
|
||||
editable: false,
|
||||
hideOnRemove: true
|
||||
}, chunter.ids.TxCommentCreate)
|
||||
},
|
||||
chunter.ids.TxCommentCreate
|
||||
)
|
||||
|
||||
// We need to define this one, to hide default attached object removed case
|
||||
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
|
||||
builder.createDoc(
|
||||
activity.class.TxViewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
objectClass: chunter.class.Backlink,
|
||||
icon: chunter.icon.Chunter,
|
||||
txClass: core.class.TxRemoveDoc,
|
||||
display: 'inline',
|
||||
hideOnRemove: true
|
||||
}, chunter.ids.TxBacklinkRemove)
|
||||
},
|
||||
chunter.ids.TxBacklinkRemove
|
||||
)
|
||||
}
|
||||
|
||||
export { chunterOperation } from './migration'
|
||||
|
@ -83,6 +83,20 @@ export function createModel (builder: Builder): void {
|
||||
hidden: false,
|
||||
navigatorModel: {
|
||||
specials: [
|
||||
{
|
||||
id: 'spaceBrowser',
|
||||
component: workbench.component.SpaceBrowser,
|
||||
icon: workbench.icon.Search,
|
||||
label: lead.string.FunnelBrowser,
|
||||
position: 'top',
|
||||
spaceClass: chunter.class.Channel,
|
||||
componentProps: {
|
||||
_class: lead.class.Funnel,
|
||||
label: lead.string.FunnelBrowser,
|
||||
createItemDialog: lead.component.CreateFunnel,
|
||||
createItemLabel: lead.string.CreateFunnel
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'customers',
|
||||
label: lead.string.Customers,
|
||||
|
@ -30,7 +30,8 @@ export default mergeIds(leadId, lead, {
|
||||
Lead: '' as IntlString,
|
||||
Title: '' as IntlString,
|
||||
Assignee: '' as IntlString,
|
||||
ManageFunnelStatuses: '' as IntlString
|
||||
ManageFunnelStatuses: '' as IntlString,
|
||||
FunnelBrowser: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
CreateFunnel: '' as AnyComponent,
|
||||
|
@ -21,11 +21,13 @@ import workbench, { workbenchId } from '@anticrm/workbench'
|
||||
export default mergeIds(workbenchId, workbench, {
|
||||
component: {
|
||||
ApplicationPresenter: '' as AnyComponent,
|
||||
Archive: '' as AnyComponent
|
||||
Archive: '' as AnyComponent,
|
||||
SpaceBrowser: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
Archive: '' as IntlString,
|
||||
Application: '' as IntlString
|
||||
Application: '' as IntlString,
|
||||
SpaceBrowser: '' as IntlString
|
||||
},
|
||||
function: {
|
||||
HasArchiveSpaces: '' as Resource<(spaces: Space[]) => boolean>
|
||||
|
@ -15,6 +15,11 @@
|
||||
"Search": "Search...",
|
||||
"Unassigned": "Unassigned",
|
||||
"CreateMore": "Create more",
|
||||
"NumberMembers": "{count, plural, =0 {no members} =1 {1 member} other {# members}}"
|
||||
"NumberMembers": "{count, plural, =0 {no members} =1 {1 member} other {# members}}",
|
||||
"InThis": "In this {space}",
|
||||
"NoMatchesInThis": "No matches in this {space}",
|
||||
"NoMatchesFound": "No matches found",
|
||||
"NotInThis": "Not in this {space}",
|
||||
"Add": "Add"
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,11 @@
|
||||
"Search": "Поиск...",
|
||||
"Unassigned": "Не назначен",
|
||||
"CreateMore": "Создать еще",
|
||||
"NumberMembers": "{count, plural, =0 {нет участников} =1 {1 участник} other {# участника}}"
|
||||
"NumberMembers": "{count, plural, =0 {нет участников} =1 {1 участник} other {# участника}}",
|
||||
"InThis": "В этом {space}",
|
||||
"NoMatchesInThis": "В этом {space} совпадения не обнаружены",
|
||||
"NoMatchesFound": "Не найдено соответсвий",
|
||||
"NotInThis": "Не в этом {space}",
|
||||
"Add": "Добавить"
|
||||
}
|
||||
}
|
||||
|
@ -14,23 +14,99 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// import { Class, Doc, Ref, Space } from '@anticrm/core'
|
||||
// import { getClient } from '@anticrm/presentation'
|
||||
import { Label } from '@anticrm/ui'
|
||||
// import { Table } from '@anticrm/view-resources'
|
||||
import contact,{ Employee,EmployeeAccount } from '@anticrm/contact'
|
||||
import { Account,DocumentQuery,Ref,SortingOrder,Space } from '@anticrm/core'
|
||||
import { translate } from '@anticrm/platform'
|
||||
import { Label,Scroller,SearchEdit } from '@anticrm/ui'
|
||||
import presentation from '../plugin'
|
||||
import { getClient } from '../utils'
|
||||
import UserInfo from './UserInfo.svelte'
|
||||
|
||||
// const client = getClient()
|
||||
export let space: Space
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
$: label = hierarchy.getClass(space._class).label
|
||||
let spaceClass = ''
|
||||
$: { translate(label, {}).then((p) => spaceClass = p.toLowerCase()) }
|
||||
let search: string = ''
|
||||
$: isSearch = search.trim().length
|
||||
let members: Set<Ref<Employee>> = new Set<Ref<Employee>>()
|
||||
|
||||
async function getUsers (accounts: Ref<Account>[], search: string): Promise<Employee[]> {
|
||||
const query: DocumentQuery<EmployeeAccount> = isSearch > 0 ? { name: { $like: '%' + search + '%' } } : { _id: { $in: accounts as Ref<EmployeeAccount>[] } }
|
||||
const employess = await client.findAll(contact.class.EmployeeAccount, query)
|
||||
members = new Set(employess.filter((p) => accounts.includes(p._id)).map((p) => p.employee))
|
||||
return await client.findAll(contact.class.Employee, {
|
||||
_id: { $in: employess.map((e) => e.employee) }
|
||||
}, { sort: { name: SortingOrder.Descending } })
|
||||
}
|
||||
|
||||
async function add (employee: Ref<Employee>): Promise<void> {
|
||||
const account = await client.findOne(contact.class.EmployeeAccount, { employee })
|
||||
if (account === undefined) return
|
||||
await client.update(space, {
|
||||
$push: {
|
||||
members: account._id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex-col">
|
||||
<div class="flex-row-center">
|
||||
<span class="title"><Label label={presentation.string.Members} /></span>
|
||||
<div class="flex-col h-full">
|
||||
<div class="ml-8 mr-8 mb-6 mt-4"><SearchEdit bind:value={search} /></div>
|
||||
{#await getUsers(space.members, search) then users}
|
||||
{@const current = users.filter((p) => members.has(p._id))}
|
||||
{@const foreign = users.filter((p) => !members.has(p._id))}
|
||||
{#if isSearch && !foreign.length && !current.length}
|
||||
<div class="fs-title flex-center mt-10">
|
||||
<Label label={presentation.string.NoMatchesFound} />
|
||||
</div>
|
||||
<!-- TODO: implement Members -->
|
||||
{:else}
|
||||
<Scroller>
|
||||
{#if isSearch}
|
||||
<div class="pr-8 pl-8"><Label label={presentation.string.InThis} params={{ space: spaceClass }} /></div>
|
||||
{#if !current.length}
|
||||
<div class="fs-title pl-8 mb-4 mt-4">
|
||||
<Label label={presentation.string.NoMatchesInThis} params={{ space: spaceClass }} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#each current as person}
|
||||
<div class="item fs-title"><UserInfo size={'medium'} value={person} /></div>
|
||||
{/each}
|
||||
{#if foreign.length}
|
||||
<div class="mt-4 notIn h-full">
|
||||
<div class="divider w-full mb-4" />
|
||||
<div class="pr-8 pl-8"><Label label={presentation.string.NotInThis} params={{ space: spaceClass }} /></div>
|
||||
{#each foreign as person}
|
||||
<div class="item flex-between">
|
||||
<div class="fs-title"><UserInfo size={'medium'} value={person} /></div>
|
||||
<div class="over-underline" on:click={() => add(person._id)}><Label label={presentation.string.Add} /></div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</Scroller>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.notIn {
|
||||
background-color: var(--theme-bg-accent-color);
|
||||
}
|
||||
|
||||
.divider {
|
||||
background-color: var(--theme-dialog-divider);
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.item {
|
||||
color: var(--caption-color);
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 2rem;
|
||||
|
||||
&:hover, &:focus { background-color: var(--popup-bg-hover); }
|
||||
}
|
||||
</style>
|
||||
|
@ -44,7 +44,12 @@ export default plugin(presentationId, {
|
||||
Search: '' as IntlString,
|
||||
Unassigned: '' as IntlString,
|
||||
CreateMore: '' as IntlString,
|
||||
NumberMembers: '' as IntlString
|
||||
NumberMembers: '' as IntlString,
|
||||
InThis: '' as IntlString,
|
||||
NoMatchesInThis: '' as IntlString,
|
||||
NoMatchesFound: '' as IntlString,
|
||||
NotInThis: '' as IntlString,
|
||||
Add: '' as IntlString
|
||||
},
|
||||
metadata: {
|
||||
RequiredVersion: '' as Metadata<string>
|
||||
|
@ -353,6 +353,7 @@ p:last-child { margin-block-end: 0; }
|
||||
.pr-2 { padding-right: .5rem; }
|
||||
.pr-3 { padding-right: .75rem; }
|
||||
.pr-4 { padding-right: 1rem; }
|
||||
.pr-8 { padding-right: 2rem; }
|
||||
.pr-24 { padding-right: 6rem; }
|
||||
.pt-2 { padding-top: .5rem; }
|
||||
.pt-3 { padding-top: .75rem; }
|
||||
|
@ -49,6 +49,8 @@
|
||||
"AddToSaved": "Add to saved",
|
||||
"RemoveFromSaved": "Remove from saved",
|
||||
"EmptySavedHeader": "Add messages to come back to later",
|
||||
"EmptySavedText": "Tick off your to-dos or save something for another time. Only you can see your saved items, so use them however you like."
|
||||
"EmptySavedText": "Tick off your to-dos or save something for another time. Only you can see your saved items, so use them however you like.",
|
||||
"LeaveChannel": "Leave channel",
|
||||
"ChannelBrowser": "Channel browser"
|
||||
}
|
||||
}
|
@ -48,6 +48,8 @@
|
||||
"AddToSaved": "Добавить в сохраненные",
|
||||
"RemoveFromSaved": "Удалить из сохраненных",
|
||||
"EmptySavedHeader": "Добавляйте сообщения и файлы, чтобы вернуться к ним позже",
|
||||
"EmptySavedText": "Пометьте свои задачи или сохраните что-нибудь на потом. Только вы можете просматривать свои сохраненные объекты, поэтому используйте их как угодно."
|
||||
"EmptySavedText": "Пометьте свои задачи или сохраните что-нибудь на потом. Только вы можете просматривать свои сохраненные объекты, поэтому используйте их как угодно.",
|
||||
"LeaveChannel": "Покинуть канал",
|
||||
"ChannelBrowser": "Браузер каналов"
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@
|
||||
export let _id: Ref<Channel>
|
||||
export let _class: Ref<Class<Channel>>
|
||||
|
||||
let channel: Channel
|
||||
let channel: Channel | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -81,14 +81,15 @@
|
||||
{/each}
|
||||
<div class="ac-tabs__empty" />
|
||||
</div>
|
||||
<Scroller padding>
|
||||
{#if channel}
|
||||
{#if selectedTabIndex === 0}
|
||||
<EditChannelDescriptionTab {channel} {_id} {_class} />
|
||||
<Scroller padding>
|
||||
<EditChannelDescriptionTab {channel} on:close />
|
||||
</Scroller>
|
||||
{:else if selectedTabIndex === 1}
|
||||
<!-- Channel members -->
|
||||
<Members />
|
||||
<Members space={channel} />
|
||||
{:else if selectedTabIndex === 2}
|
||||
<EditChannelSettingsTab {channel} on:close />
|
||||
{/if}
|
||||
</Scroller>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -96,7 +96,7 @@
|
||||
|
||||
<style lang="scss">
|
||||
.group {
|
||||
border: 1px solid var(--theme-bg-focused-border);
|
||||
border: 1px solid var(--theme-button-border-hovered);
|
||||
border-radius: 12px;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
@ -15,34 +15,17 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Channel } from '@anticrm/chunter'
|
||||
import type { Class, Ref } from '@anticrm/core'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { EditBox } from '@anticrm/ui'
|
||||
|
||||
import { getCurrentAccount } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Button, EditBox } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import chunter from '../plugin'
|
||||
import EditChannelDescriptionAttachments from './EditChannelDescriptionAttachments.svelte'
|
||||
|
||||
export let _id: Ref<Channel>
|
||||
export let _class: Ref<Class<Channel>>
|
||||
|
||||
export let channel: Channel | undefined
|
||||
export let channel: Channel
|
||||
|
||||
const client = getClient()
|
||||
const clazz = client.getHierarchy().getClass(_class)
|
||||
|
||||
const query = createQuery()
|
||||
|
||||
function onNameChange (ev: Event) {
|
||||
const value = (ev.target as HTMLInputElement).value
|
||||
if (value.trim().length > 0) {
|
||||
client.updateDoc(_class, channel!.space, channel!._id, { name: value })
|
||||
} else {
|
||||
// Just refresh value
|
||||
query.query(chunter.class.Channel, { _id }, (result) => {
|
||||
channel = result[0]
|
||||
})
|
||||
}
|
||||
}
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function onTopicChange (ev: Event) {
|
||||
const newTopic = (ev.target as HTMLInputElement).value
|
||||
@ -53,19 +36,17 @@
|
||||
const newDescription = (ev.target as HTMLInputElement).value
|
||||
client.update(channel!, { description: newDescription })
|
||||
}
|
||||
|
||||
async function leaveChannel (): Promise<void> {
|
||||
await client.update(channel, {
|
||||
$pull: { members: getCurrentAccount()._id }
|
||||
})
|
||||
dispatch('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if channel}
|
||||
<div class="flex-col flex-gap-3">
|
||||
<EditBox
|
||||
label={clazz.label}
|
||||
icon={clazz.icon}
|
||||
bind:value={channel.name}
|
||||
placeholder={clazz.label}
|
||||
maxWidth="39rem"
|
||||
focus
|
||||
on:change={onNameChange}
|
||||
/>
|
||||
<EditBox
|
||||
label={chunter.string.Topic}
|
||||
bind:value={channel.topic}
|
||||
@ -82,6 +63,14 @@
|
||||
focus
|
||||
on:change={onDescriptionChange}
|
||||
/>
|
||||
<Button
|
||||
label={chunter.string.LeaveChannel}
|
||||
justify={'left'}
|
||||
size={'x-large'}
|
||||
on:click={() => {
|
||||
leaveChannel()
|
||||
}}
|
||||
/>
|
||||
<EditChannelDescriptionAttachments {channel} />
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -11,10 +11,11 @@
|
||||
</script>
|
||||
|
||||
{#if channel}
|
||||
<div class="flex-col flex-gap-3">
|
||||
<div class="mt-4 ml-8 mr-8 flex-col p-4">
|
||||
<Button
|
||||
label={chunter.string.ArchiveChannel}
|
||||
justify={'left'}
|
||||
size={'x-large'}
|
||||
on:click={() => {
|
||||
ArchiveChannel(channel, () => dispatch('close'))
|
||||
}}
|
||||
|
@ -65,6 +65,8 @@ export default mergeIds(chunterId, chunter, {
|
||||
AddToSaved: '' as IntlString,
|
||||
RemoveFromSaved: '' as IntlString,
|
||||
EmptySavedHeader: '' as IntlString,
|
||||
EmptySavedText: '' as IntlString
|
||||
EmptySavedText: '' as IntlString,
|
||||
LeaveChannel: '' as IntlString,
|
||||
ChannelBrowser: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -20,6 +20,7 @@
|
||||
"Assignee": "Assignee",
|
||||
"Title": "Title",
|
||||
"LeadPlaceholder": "The simple lead",
|
||||
"ManageFunnelStatuses": "Manage funnel statuses"
|
||||
"ManageFunnelStatuses": "Manage funnel statuses",
|
||||
"FunnelBrowser": "Funnel browser"
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@
|
||||
"Assignee": "Назначена",
|
||||
"Title": "Загаловок",
|
||||
"LeadPlaceholder": "Простая сделка",
|
||||
"ManageFunnelStatuses": "Управление статусами воронки"
|
||||
"ManageFunnelStatuses": "Управление статусами воронки",
|
||||
"FunnelBrowser": "Браузер воронок"
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
import { Attachments } from '@anticrm/attachment-resources'
|
||||
import type { Ref } from '@anticrm/core'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { AttributesBar, createQuery, getClient } from '@anticrm/presentation'
|
||||
import { AttributesBar, createQuery, getClient, Members } from '@anticrm/presentation'
|
||||
import { Vacancy } from '@anticrm/recruit'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { ActionIcon, Component, EditBox, Grid, Icon, IconClose, Label, Scroller, ToggleWithLabel } from '@anticrm/ui'
|
||||
@ -104,9 +104,7 @@
|
||||
</div>
|
||||
</Scroller>
|
||||
{:else if selected === 1}
|
||||
<Scroller padding>
|
||||
<ToggleWithLabel label={recruit.string.ThisVacancyIsPrivate} description={recruit.string.MakePrivateDescription}/>
|
||||
</Scroller>
|
||||
<Members space={object} />
|
||||
{:else if selected === 2}
|
||||
<Component is={activity.component.Activity} props={{ object, transparent: true }} />
|
||||
{/if}
|
||||
|
@ -43,7 +43,7 @@ export { default as TableBrowser } from './components/TableBrowser.svelte'
|
||||
export * from './context'
|
||||
export * from './selection'
|
||||
export { buildModel, getCollectionCounter, getObjectPresenter, LoadingProps } from './utils'
|
||||
export { Table, TableView, EditDoc, ColorsPopup, Menu }
|
||||
export { Table, TableView, EditDoc, ColorsPopup, Menu, SpacePresenter }
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
actionImpl: actionImpl,
|
||||
|
@ -1,2 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="search" viewBox="0 0 16 16">
|
||||
<path d="M14.4,13.6L11.7,11c0.8-1,1.3-2.2,1.3-3.5c0-3-2.5-5.5-5.5-5.5C4.5,2,2,4.5,2,7.5c0,3,2.5,5.5,5.5,5.5 c1.3,0,2.6-0.5,3.5-1.3l2.6,2.6c0.2,0.2,0.5,0.2,0.7,0C14.5,14.2,14.5,13.8,14.4,13.6z M3,7.5C3,5,5,3,7.5,3S12,5,12,7.5 S10,12,7.5,12S3,10,3,7.5z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 71 B After Width: | Height: | Size: 384 B |
@ -9,6 +9,11 @@
|
||||
"Open": "Open",
|
||||
"General": "General",
|
||||
"Members": "Members",
|
||||
"Application": "Application"
|
||||
"Application": "Application",
|
||||
"View": "View",
|
||||
"Leave": "Leave",
|
||||
"Joined": "Joined",
|
||||
"Join": "Join",
|
||||
"BrowseSpaces": "Browse spaces"
|
||||
}
|
||||
}
|
@ -9,6 +9,11 @@
|
||||
"Open": "Открыть",
|
||||
"General": "Общее",
|
||||
"Members": "Участники",
|
||||
"Application": "Приложение"
|
||||
"Application": "Приложение",
|
||||
"View": "Посмотреть",
|
||||
"Leave": "Покинуть",
|
||||
"Joined": "Вы присоеденились",
|
||||
"Join": "Присоедениться",
|
||||
"BrowseSpaces": "Обзор пространств"
|
||||
}
|
||||
}
|
@ -13,7 +13,12 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { addStringsLoader } from '@anticrm/platform'
|
||||
import { workbenchId } from '@anticrm/workbench'
|
||||
import { addStringsLoader, loadMetadata } from '@anticrm/platform'
|
||||
import workbench, { workbenchId } from '@anticrm/workbench'
|
||||
|
||||
const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(workbench.icon, {
|
||||
Search: `${icons}#search`
|
||||
})
|
||||
|
||||
addStringsLoader(workbenchId, async (lang: string) => await import(`../lang/${lang}.json`))
|
||||
|
@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import core, { Doc, Ref, SortingOrder, Space } from '@anticrm/core'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
@ -45,9 +44,14 @@
|
||||
core.class.Space,
|
||||
{
|
||||
_class: { $in: getSpecialSpaceClass(model) }
|
||||
// temp disabled, need way for default spaces
|
||||
// members: getCurrentAccount()._id
|
||||
},
|
||||
(result) => { spaces = result },
|
||||
{ sort: { name: SortingOrder.Ascending } })
|
||||
(result) => {
|
||||
spaces = result
|
||||
},
|
||||
{ sort: { name: SortingOrder.Ascending } }
|
||||
)
|
||||
}
|
||||
|
||||
let topSpecials: SpecialNavModel[] = []
|
||||
@ -58,10 +62,14 @@
|
||||
const preferenceQuery = createQuery()
|
||||
|
||||
preferenceQuery.query(preferece.class.SpacePreference, {}, (res) => {
|
||||
preferences = new Map(res.map((r) => { return [r.attachedTo, r] }))
|
||||
preferences = new Map(
|
||||
res.map((r) => {
|
||||
return [r.attachedTo, r]
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
async function update (model: NavigatorModel, spaces: Space[], preferences: Map<Ref<Doc>, SpacePreference>) {
|
||||
async function update(model: NavigatorModel, spaces: Space[], preferences: Map<Ref<Doc>, SpacePreference>) {
|
||||
if (model.specials !== undefined) {
|
||||
topSpecials = await getSpecials(model.specials, 'top', spaces)
|
||||
bottomSpecials = await getSpecials(model.specials, 'bottom', spaces)
|
||||
@ -75,7 +83,11 @@
|
||||
|
||||
$: if (model) update(model, spaces, preferences)
|
||||
|
||||
async function getSpecials (specials: SpecialNavModel[], state: 'top' | 'bottom', spaces: Space[]): Promise<SpecialNavModel[]> {
|
||||
async function getSpecials(
|
||||
specials: SpecialNavModel[],
|
||||
state: 'top' | 'bottom',
|
||||
spaces: Space[]
|
||||
): Promise<SpecialNavModel[]> {
|
||||
const result: SpecialNavModel[] = []
|
||||
for (const sp of specials) {
|
||||
if ((sp.position ?? 'top') === state) {
|
||||
@ -98,13 +110,25 @@
|
||||
<Scroller>
|
||||
{#if model.specials}
|
||||
{#each topSpecials as special}
|
||||
<SpecialElement label={special.label} icon={special.icon} on:click={() => dispatch('special', special.id)} selected={special.id === currentSpecial} indent={'ml-2'} />
|
||||
<SpecialElement
|
||||
label={special.label}
|
||||
icon={special.icon}
|
||||
on:click={() => dispatch('special', special.id)}
|
||||
selected={special.id === currentSpecial}
|
||||
indent={'ml-2'}
|
||||
/>
|
||||
{/each}
|
||||
{#if topSpecials.length > 0 && bottomSpecials.length > 0}
|
||||
<TreeSeparator />
|
||||
{/if}
|
||||
{#each bottomSpecials as special}
|
||||
<SpecialElement label={special.label} icon={special.icon} on:click={() => dispatch('special', special.id)} selected={special.id === currentSpecial} indent={'ml-2'} />
|
||||
<SpecialElement
|
||||
label={special.label}
|
||||
icon={special.icon}
|
||||
on:click={() => dispatch('special', special.id)}
|
||||
selected={special.id === currentSpecial}
|
||||
indent={'ml-2'}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
@ -115,7 +139,14 @@
|
||||
{/if}
|
||||
|
||||
{#each model.spaces as m (m.label)}
|
||||
<SpacesNav spaces={shownSpaces.filter(it => hierarchy.isDerived(it._class, m.spaceClass))} {currentSpace} model={m} on:space {currentSpecial}/>
|
||||
<SpacesNav
|
||||
spaces={shownSpaces.filter((it) => hierarchy.isDerived(it._class, m.spaceClass))}
|
||||
{currentSpace}
|
||||
hasSpaceBrowser={model.specials?.find((p) => p.id === 'spaceBrowser') !== undefined}
|
||||
model={m}
|
||||
on:space
|
||||
{currentSpecial}
|
||||
/>
|
||||
{/each}
|
||||
<div class="antiNav-space" />
|
||||
</Scroller>
|
||||
|
156
plugins/workbench-resources/src/components/SpaceBrowser.svelte
Normal file
156
plugins/workbench-resources/src/components/SpaceBrowser.svelte
Normal file
@ -0,0 +1,156 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Class,FindOptions,getCurrentAccount,Ref,SortingOrder,SortingQuery,Space } from '@anticrm/core'
|
||||
import { AnyComponent, Button, getCurrentLocation, Icon, Label, navigate, Scroller, SearchEdit, showPopup } from '@anticrm/ui'
|
||||
import presentation, { createQuery, getClient } from '@anticrm/presentation'
|
||||
import plugin from '../plugin'
|
||||
import { SpacePresenter } from '@anticrm/view-resources'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { classIcon } from '../utils'
|
||||
|
||||
export let _class: Ref<Class<Space>>
|
||||
export let label: IntlString
|
||||
export let createItemDialog: AnyComponent | undefined
|
||||
export let createItemLabel: IntlString = presentation.string.Create
|
||||
|
||||
const me = getCurrentAccount()._id
|
||||
const client = getClient()
|
||||
const spaceQuery = createQuery()
|
||||
let search: string = ''
|
||||
let sort: SortingQuery<Space> = {
|
||||
name: SortingOrder.Ascending
|
||||
}
|
||||
|
||||
let spaces: Space[] = []
|
||||
|
||||
$: update(search, sort)
|
||||
|
||||
async function update (search: string, sort: SortingQuery<Space>): Promise<void> {
|
||||
const query = search.trim().length > 0 ? { name: { $like: '%' + search + '%' } }: {}
|
||||
const options: FindOptions<Space> = {
|
||||
sort
|
||||
}
|
||||
spaceQuery.query(_class, query, (res) => {
|
||||
spaces = res.filter((p) => !p.private || p.members.includes(me))
|
||||
}, options)
|
||||
}
|
||||
|
||||
|
||||
function showCreateDialog (ev: Event) {
|
||||
showPopup(createItemDialog as AnyComponent, { }, 'middle')
|
||||
}
|
||||
|
||||
async function join (space: Space): Promise<void> {
|
||||
if (space.members.includes(me)) return
|
||||
await client.update(space, {
|
||||
$push: {
|
||||
members: me
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function leave (space: Space): Promise<void> {
|
||||
if (!space.members.includes(me)) return
|
||||
await client.update(space, {
|
||||
$pull: {
|
||||
members: me
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function view (space: Space): Promise<void> {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = space._id
|
||||
navigate(loc)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="ac-header full divide">
|
||||
<div class="ac-header__wrap-title">
|
||||
<span class="ac-header__title"><Label {label} /></span>
|
||||
</div>
|
||||
{#if createItemDialog}
|
||||
<Button label={createItemLabel} on:click={(ev) => showCreateDialog(ev)}/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ml-8 mr-8 mt-4"><SearchEdit bind:value={search} /></div>
|
||||
<Scroller padding>
|
||||
<div class="flex-col">
|
||||
{#each spaces as space (space._id)}
|
||||
{@const icon = classIcon(client, space._class)}
|
||||
{@const joined = space.members.includes(me)}
|
||||
<div class="divider"></div>
|
||||
<div class="item flex-between">
|
||||
<div>
|
||||
<div class="fs-title flex">
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} />
|
||||
{/if}
|
||||
<SpacePresenter value={space} />
|
||||
</div>
|
||||
<div>
|
||||
{#if joined}
|
||||
<Label label={plugin.string.Joined} />
|
||||
·
|
||||
{/if}
|
||||
{space.members.length}
|
||||
·
|
||||
{space.description}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tools flex">
|
||||
{#if joined}
|
||||
<Button size={'x-large'} label={plugin.string.Leave} on:click={() => leave(space)}/>
|
||||
{:else}
|
||||
<div class="mr-2">
|
||||
<Button size={'x-large'} label={plugin.string.View} on:click={() => view(space)}/>
|
||||
</div>
|
||||
<Button size={'x-large'} kind={'primary'} label={plugin.string.Join} on:click={() => join(space)}/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex-center mt-10">
|
||||
<Button size={'x-large'} kind={'primary'} label={createItemLabel} on:click={(ev) => showCreateDialog(ev)}/>
|
||||
</div>
|
||||
</div>
|
||||
</Scroller>
|
||||
|
||||
<style lang="scss">
|
||||
.divider {
|
||||
background-color: var(--theme-dialog-divider);
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.item {
|
||||
color: var(--caption-color);
|
||||
cursor: pointer;
|
||||
padding: 1rem 0.75rem;
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: var(--popup-bg-hover);
|
||||
|
||||
.tools {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.tools {
|
||||
position: relative;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -13,12 +13,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Class, Ref, Space } from '@anticrm/core'
|
||||
import core from '@anticrm/core'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { createQuery, getClient, Members } from '@anticrm/presentation'
|
||||
import { ActionIcon, EditBox, Grid, Icon, IconClose, Label, Scroller } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import workbench from '../../plugin'
|
||||
@ -37,24 +36,32 @@
|
||||
const clazz = client.getHierarchy().getClass(_class)
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(core.class.Space, { _id }, result => { space = result[0] })
|
||||
$: query.query(core.class.Space, { _id }, (result) => {
|
||||
space = result[0]
|
||||
})
|
||||
|
||||
const tabs: IntlString[] = [workbench.string.General, workbench.string.Members]
|
||||
let selected = 0
|
||||
|
||||
function onNameChange (ev: Event) {
|
||||
function onNameChange(ev: Event) {
|
||||
const value = (ev.target as HTMLInputElement).value
|
||||
if (value.trim().length > 0) {
|
||||
client.updateDoc(_class, space.space, space._id, { name: value })
|
||||
} else {
|
||||
// Just refresh value
|
||||
query.query(core.class.Space, { _id }, result => { space = result[0] })
|
||||
query.query(core.class.Space, { _id }, (result) => {
|
||||
space = result[0]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="antiOverlay" on:click={() => { dispatch('close') }}/>
|
||||
<div
|
||||
class="antiOverlay"
|
||||
on:click={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
<div class="antiDialogs antiComponent">
|
||||
<div class="ac-header short mirror divide">
|
||||
<div class="ac-header__wrap-title">
|
||||
@ -63,12 +70,25 @@
|
||||
{/if}
|
||||
<div class="ac-header__title"><Label label={clazz.label} /></div>
|
||||
</div>
|
||||
<div class="tool"><ActionIcon icon={IconClose} size={'small'} action={() => { dispatch('close') }} /></div>
|
||||
<div class="tool">
|
||||
<ActionIcon
|
||||
icon={IconClose}
|
||||
size={'small'}
|
||||
action={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ac-tabs">
|
||||
{#each tabs as tab, i}
|
||||
<div class="ac-tabs__tab" class:selected={i === selected}
|
||||
on:click={() => { selected = i }}>
|
||||
<div
|
||||
class="ac-tabs__tab"
|
||||
class:selected={i === selected}
|
||||
on:click={() => {
|
||||
selected = i
|
||||
}}
|
||||
>
|
||||
<Label label={tab} />
|
||||
</div>
|
||||
{/each}
|
||||
@ -78,13 +98,21 @@
|
||||
{#if selected === 0}
|
||||
{#if space}
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<EditBox label={clazz.label} icon={clazz.icon} bind:value={space.name} placeholder={clazz.label} maxWidth="39rem" focus on:change={onNameChange}/>
|
||||
<EditBox
|
||||
label={clazz.label}
|
||||
icon={clazz.icon}
|
||||
bind:value={space.name}
|
||||
placeholder={clazz.label}
|
||||
maxWidth="39rem"
|
||||
focus
|
||||
on:change={onNameChange}
|
||||
/>
|
||||
<!-- <AttributeBarEditor maxWidth="39rem" object={space} key="name"/> -->
|
||||
<!-- <ToggleWithLabel label={workbench.string.MakePrivate} description={workbench.string.MakePrivateDescription}/> -->
|
||||
</Grid>
|
||||
{/if}
|
||||
{:else}
|
||||
<Label label={workbench.string.Members} />
|
||||
<Members {space} />
|
||||
{/if}
|
||||
</Scroller>
|
||||
</div>
|
||||
|
@ -19,7 +19,17 @@
|
||||
import { NotificationClientImpl } from '@anticrm/notification-resources'
|
||||
import { getResource } from '@anticrm/platform'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Action, AnyComponent, IconAdd, IconEdit, showPanel, showPopup } from '@anticrm/ui'
|
||||
import {
|
||||
Action,
|
||||
AnyComponent,
|
||||
getCurrentLocation,
|
||||
IconAdd,
|
||||
IconEdit,
|
||||
IconSearch,
|
||||
navigate,
|
||||
showPanel,
|
||||
showPopup
|
||||
} from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import preference from '@anticrm/preference'
|
||||
import { getActions as getContributedActions } from '@anticrm/view-resources'
|
||||
@ -35,6 +45,7 @@
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
export let spaces: Space[]
|
||||
export let currentSpecial: string | undefined
|
||||
export let hasSpaceBrowser: boolean = false
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -46,6 +57,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
const browseSpaces: Action = {
|
||||
label: plugin.string.BrowseSpaces,
|
||||
icon: IconSearch,
|
||||
action: async (_id: Ref<Doc>, ev?: Event): Promise<void> => {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = 'spaceBrowser'
|
||||
navigate(loc)
|
||||
}
|
||||
}
|
||||
|
||||
const editSpace: Action = {
|
||||
label: plugin.string.Open,
|
||||
icon: IconEdit,
|
||||
@ -65,7 +86,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function getEditor (_class: Ref<Class<Doc>>): Promise<AnyComponent | undefined> {
|
||||
async function getEditor(_class: Ref<Class<Doc>>): Promise<AnyComponent | undefined> {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const clazz = hierarchy.getClass(_class)
|
||||
const editorMixin = hierarchy.as(clazz, view.mixin.ObjectEditor)
|
||||
@ -73,11 +94,11 @@
|
||||
return editorMixin.editor
|
||||
}
|
||||
|
||||
function selectSpace (id: Ref<Space>, spaceSpecial?: string) {
|
||||
function selectSpace(id: Ref<Space>, spaceSpecial?: string) {
|
||||
dispatch('space', { space: id, spaceSpecial })
|
||||
}
|
||||
|
||||
async function getActions (space: Space): Promise<Action[]> {
|
||||
async function getActions(space: Space): Promise<Action[]> {
|
||||
const result = [editSpace, starSpace]
|
||||
|
||||
const extraActions = await getContributedActions(client, space, core.class.Space)
|
||||
@ -100,7 +121,7 @@
|
||||
$: clazz = hierarchy.getClass(model.spaceClass)
|
||||
$: lastEditMixin = hierarchy.as(clazz, notification.mixin.SpaceLastEdit)
|
||||
|
||||
function isChanged (space: Space, lastViews: Map<Ref<Doc>, number>): boolean {
|
||||
function isChanged(space: Space, lastViews: Map<Ref<Doc>, number>): boolean {
|
||||
const field = lastEditMixin?.lastEditField
|
||||
const lastView = lastViews.get(space._id)
|
||||
if (lastView === undefined || lastView === -1) return false
|
||||
@ -110,9 +131,13 @@
|
||||
|
||||
return lastView < value
|
||||
}
|
||||
|
||||
function getParentActions(): Action[] {
|
||||
return hasSpaceBrowser ? [browseSpaces, addSpace] : [addSpace]
|
||||
}
|
||||
</script>
|
||||
|
||||
<TreeNode label={model.label} parent actions={async () => [addSpace]} indent={'ml-2'}>
|
||||
<TreeNode label={model.label} parent actions={async () => getParentActions()} indent={'ml-2'}>
|
||||
{#each spaces as space (space._id)}
|
||||
{#if model.specials}
|
||||
<TreeNode icon={model.icon} title={space.name} indent={'ml-2'} actions={() => getActions(space)}>
|
||||
|
@ -19,9 +19,10 @@ import { Resources } from '@anticrm/platform'
|
||||
import Archive from './components/Archive.svelte'
|
||||
import { Space } from '@anticrm/core'
|
||||
import SpacePanel from './components/navigator/SpacePanel.svelte'
|
||||
import SpaceBrowser from './components/SpaceBrowser.svelte'
|
||||
|
||||
function hasArchiveSpaces (spaces: Space[]): boolean {
|
||||
return spaces.find(sp => sp.archived) !== undefined
|
||||
function hasArchiveSpaces(spaces: Space[]): boolean {
|
||||
return spaces.find((sp) => sp.archived) !== undefined
|
||||
}
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
@ -29,7 +30,8 @@ export default async (): Promise<Resources> => ({
|
||||
WorkbenchApp,
|
||||
ApplicationPresenter,
|
||||
Archive,
|
||||
SpacePanel
|
||||
SpacePanel,
|
||||
SpaceBrowser
|
||||
},
|
||||
function: {
|
||||
HasArchiveSpaces: hasArchiveSpaces
|
||||
|
@ -29,7 +29,12 @@ export default mergeIds(workbenchId, workbench, {
|
||||
Archived: '' as IntlString,
|
||||
Open: '' as IntlString,
|
||||
General: '' as IntlString,
|
||||
Members: '' as IntlString
|
||||
Members: '' as IntlString,
|
||||
View: '' as IntlString,
|
||||
Leave: '' as IntlString,
|
||||
Joined: '' as IntlString,
|
||||
Join: '' as IntlString,
|
||||
BrowseSpaces: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
SpacePanel: '' as AnyComponent
|
||||
|
@ -107,6 +107,9 @@ export default plugin(workbenchId, {
|
||||
component: {
|
||||
WorkbenchApp: '' as AnyComponent
|
||||
},
|
||||
icon: {
|
||||
Search: '' as Asset
|
||||
},
|
||||
metadata: {
|
||||
PlatformTitle: '' as Metadata<string>,
|
||||
ExcludedApplications: '' as Metadata<Ref<Application>[]>
|
||||
|
Loading…
Reference in New Issue
Block a user