mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-16 21:35:10 +00:00
Chunter browser: common, channels, contact (#2011)
Signed-off-by: budaeva <irina.budaeva@xored.com>
This commit is contained in:
parent
f473bdbb57
commit
b471284c53
@ -308,7 +308,7 @@ export function createModel (builder: Builder): void {
|
||||
{
|
||||
id: 'spaceBrowser',
|
||||
component: workbench.component.SpaceBrowser,
|
||||
icon: workbench.icon.Search,
|
||||
icon: chunter.icon.Hashtag,
|
||||
label: chunter.string.ChannelBrowser,
|
||||
position: 'top',
|
||||
spaceClass: chunter.class.Channel,
|
||||
@ -351,10 +351,11 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'messagesBrowser',
|
||||
label: chunter.string.MessagesBrowser,
|
||||
component: chunter.component.MessagesBrowser,
|
||||
visibleIf: chunter.function.MessageBrowserVisible
|
||||
id: 'chunterBrowser',
|
||||
label: chunter.string.ChunterBrowser,
|
||||
icon: workbench.icon.Search,
|
||||
component: chunter.component.ChunterBrowser,
|
||||
visibleIf: chunter.function.ChunterBrowserVisible
|
||||
}
|
||||
],
|
||||
spaces: [
|
||||
@ -449,6 +450,10 @@ export function createModel (builder: Builder): void {
|
||||
builder.mixin(chunter.class.ChunterMessage, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: ['space', 'modifiedOn', 'createBy', '_class']
|
||||
})
|
||||
|
||||
builder.mixin(chunter.class.Channel, core.class.Class, view.mixin.ClassFilters, {
|
||||
filters: ['private', 'archived']
|
||||
})
|
||||
}
|
||||
|
||||
export { chunterOperation } from './migration'
|
||||
|
@ -30,7 +30,7 @@ export default mergeIds(chunterId, chunter, {
|
||||
Threads: '' as AnyComponent,
|
||||
ThreadView: '' as AnyComponent,
|
||||
SavedMessages: '' as AnyComponent,
|
||||
MessagesBrowser: '' as AnyComponent
|
||||
ChunterBrowser: '' as AnyComponent
|
||||
},
|
||||
action: {
|
||||
MarkCommentUnread: '' as Ref<Action>,
|
||||
@ -86,6 +86,6 @@ export default mergeIds(chunterId, chunter, {
|
||||
Random: '' as Ref<Channel>
|
||||
},
|
||||
function: {
|
||||
MessageBrowserVisible: '' as Resource<(spaces: Space[]) => boolean>
|
||||
ChunterBrowserVisible: '' as Resource<(spaces: Space[]) => boolean>
|
||||
}
|
||||
})
|
||||
|
@ -354,6 +354,7 @@ input.search {
|
||||
.mr-6 { margin-right: 1.5rem; }
|
||||
.mr-8 { margin-right: 2rem; }
|
||||
.mr-10 { margin-right: 2.5rem; }
|
||||
.mr-32 { margin-right: 8rem }
|
||||
.mt-0-5 { margin-top: .125rem; }
|
||||
.mt-1 { margin-top: .25rem; }
|
||||
.mt-2 { margin-top: .5rem; }
|
||||
@ -396,6 +397,7 @@ input.search {
|
||||
.pb-3 { padding-bottom: .75rem; }
|
||||
.pb-4 { padding-bottom: 1rem; }
|
||||
.pb-6 { padding-bottom: 1.5rem; }
|
||||
.pb-16 { padding-bottom: 4rem; }
|
||||
.px-2 { padding: 0 .5rem; }
|
||||
.px-3 { padding: 0 .75rem; }
|
||||
.px-4 { padding: 0 1rem; }
|
||||
@ -464,6 +466,7 @@ input.search {
|
||||
.w-16 { width: 4rem; }
|
||||
.w-22 { width: 5.5rem; }
|
||||
.w-24 { width: 6rem; }
|
||||
.w-32 { width: 8rem; }
|
||||
.w-60 { width: 15rem; }
|
||||
.w-85 { width: 21.25rem; }
|
||||
.w-165 { width: 41.25rem; }
|
||||
|
@ -35,6 +35,8 @@
|
||||
import FileBrowserFilters from './FileBrowserFilters.svelte'
|
||||
import FileBrowserSortMenu from './FileBrowserSortMenu.svelte'
|
||||
|
||||
export let withHeader: boolean = true
|
||||
|
||||
const client = getClient()
|
||||
const loc = getCurrentLocation()
|
||||
const spaceId: Ref<Space> | undefined = loc.query?.spaceId as Ref<Space> | undefined
|
||||
@ -48,7 +50,7 @@
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
let selectedParticipants: Ref<Employee>[] = [currentUser.employee]
|
||||
let selectedSpaces: Ref<Space>[] = []
|
||||
let searchQuery: string = ''
|
||||
export let search: string = ''
|
||||
let isLoading = false
|
||||
|
||||
let attachments: Attachment[] = []
|
||||
@ -58,7 +60,7 @@
|
||||
let selectedFileTypeId = 'typeAny'
|
||||
let isListDisplayMode = true
|
||||
|
||||
$: fetch(searchQuery, selectedSort, selectedFileTypeId, selectedDateId, selectedParticipants, selectedSpaces)
|
||||
$: fetch(search, selectedSort, selectedFileTypeId, selectedDateId, selectedParticipants, selectedSpaces)
|
||||
|
||||
async function fetch (
|
||||
searchQuery_: string,
|
||||
@ -108,12 +110,14 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="ac-header full divide">
|
||||
<div class="ac-header__wrap-title">
|
||||
<span class="ac-header__title"><Label label={attachment.string.FileBrowser} /></span>
|
||||
{#if withHeader}
|
||||
<div class="ac-header full divide">
|
||||
<div class="ac-header__wrap-title">
|
||||
<span class="ac-header__title"><Label label={attachment.string.FileBrowser} /></span>
|
||||
</div>
|
||||
<EditWithIcon icon={IconSearch} bind:value={search} placeholder={ui.string.SearchDots} />
|
||||
</div>
|
||||
<EditWithIcon icon={IconSearch} bind:value={searchQuery} placeholder={ui.string.SearchDots} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="ac-header full">
|
||||
<FileBrowserFilters
|
||||
{requestedSpaceClasses}
|
||||
|
@ -43,7 +43,8 @@ export {
|
||||
AttachmentRefInput,
|
||||
AttachmentList,
|
||||
AttachmentDocList,
|
||||
FileDownload
|
||||
FileDownload,
|
||||
FileBrowser
|
||||
}
|
||||
|
||||
export enum FileBrowserSortMode {
|
||||
|
@ -62,6 +62,9 @@
|
||||
"ConvertToPrivate": "Convert to private channel",
|
||||
"MessagesBrowser": "Messages browser",
|
||||
"CreateBy": "Create by",
|
||||
"ThreadMessage": "Thread message"
|
||||
"ThreadMessage": "Thread message",
|
||||
"ChunterBrowser": "Search",
|
||||
"Messages": "Messages",
|
||||
"NoResults": "No results"
|
||||
}
|
||||
}
|
@ -61,6 +61,9 @@
|
||||
"ConvertToPrivate": "Конвертировать в закрытый канал",
|
||||
"MessagesBrowser": "Браузер сообщений",
|
||||
"CreateBy": "Создано пользователем",
|
||||
"ThreadMessage": "Сообщение в обсуждении"
|
||||
"ThreadMessage": "Сообщение в обсуждении",
|
||||
"ChunterBrowser": "Поиск",
|
||||
"Messages": "Сообщения",
|
||||
"NoResults": "Нет результатов"
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@
|
||||
"@anticrm/view-resources": "~0.6.0",
|
||||
"@anticrm/view": "~0.6.0",
|
||||
"@anticrm/workbench": "~0.6.1",
|
||||
"@anticrm/preference": "~0.6.0"
|
||||
"@anticrm/preference": "~0.6.0",
|
||||
"@anticrm/workbench-resources": "~0.6.1"
|
||||
}
|
||||
}
|
||||
|
134
plugins/chunter-resources/src/components/ChunterBrowser.svelte
Normal file
134
plugins/chunter-resources/src/components/ChunterBrowser.svelte
Normal file
@ -0,0 +1,134 @@
|
||||
<script lang="ts">
|
||||
import attachment from '@anticrm/attachment'
|
||||
import { FileBrowser } from '@anticrm/attachment-resources'
|
||||
import { Button } from '@anticrm/ui'
|
||||
import workbench from '@anticrm/workbench'
|
||||
import { SpaceBrowser } from '@anticrm/workbench-resources'
|
||||
import Header from './Header.svelte'
|
||||
import contact from '@anticrm/contact-resources/src/plugin'
|
||||
import { EmployeeBrowser } from '@anticrm/contact-resources'
|
||||
import { userSearch } from '../index'
|
||||
import plugin from '../plugin'
|
||||
import { SearchType } from '../utils'
|
||||
import MessagesBrowser from './MessagesBrowser.svelte'
|
||||
import { FilterButton } from '@anticrm/view-resources'
|
||||
import { Filter } from '@anticrm/view'
|
||||
|
||||
let userSearch_: string = ''
|
||||
userSearch.subscribe((v) => (userSearch_ = v))
|
||||
|
||||
let searchType: SearchType = SearchType.Messages
|
||||
|
||||
const components = [
|
||||
{ searchType: SearchType.Messages, component: MessagesBrowser, filterClass: plugin.class.ChunterMessage },
|
||||
{
|
||||
searchType: SearchType.Channels,
|
||||
component: SpaceBrowser,
|
||||
filterClass: plugin.class.Channel,
|
||||
props: {
|
||||
_class: plugin.class.Channel,
|
||||
label: plugin.string.ChannelBrowser,
|
||||
withFilterButton: false
|
||||
}
|
||||
},
|
||||
{
|
||||
searchType: SearchType.Files,
|
||||
component: FileBrowser,
|
||||
props: {
|
||||
requestedSpaceClasses: [plugin.class.Channel, plugin.class.DirectMessage]
|
||||
}
|
||||
},
|
||||
{ searchType: SearchType.Contacts, component: EmployeeBrowser, filterClass: contact.class.Employee }
|
||||
]
|
||||
|
||||
let filters: Filter[] = []
|
||||
</script>
|
||||
|
||||
<div class="flex-col h-full">
|
||||
<div class="ac-header divide full">
|
||||
<Header icon={workbench.icon.Search} intlLabel={plugin.string.ChunterBrowser} />
|
||||
</div>
|
||||
<div class="h-full browser">
|
||||
<div class="pb-16 component">
|
||||
<div class="h-full">
|
||||
{#if components[searchType].component}
|
||||
<svelte:component
|
||||
this={components[searchType].component}
|
||||
withHeader={false}
|
||||
bind:search={userSearch_}
|
||||
bind:filters
|
||||
{...components[searchType].props}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 bar">
|
||||
<div class="w-32 flex-center"><FilterButton _class={components[searchType].filterClass} bind:filters /></div>
|
||||
<div class="flex-center w-full mr-32 buttons">
|
||||
<div class="ml-1 p-1 btn">
|
||||
<Button
|
||||
label={plugin.string.Messages}
|
||||
selected={searchType === SearchType.Messages}
|
||||
kind="transparent"
|
||||
on:click={() => {
|
||||
searchType = SearchType.Messages
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-1 p-1 btn">
|
||||
<Button
|
||||
label={plugin.string.Channels}
|
||||
kind="transparent"
|
||||
selected={searchType === SearchType.Channels}
|
||||
on:click={() => {
|
||||
searchType = SearchType.Channels
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-1 p-1 btn">
|
||||
<Button
|
||||
label={attachment.string.Files}
|
||||
kind="transparent"
|
||||
selected={searchType === SearchType.Files}
|
||||
on:click={() => {
|
||||
searchType = SearchType.Files
|
||||
filters = []
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-1 p-1 btn">
|
||||
<Button
|
||||
kind="transparent"
|
||||
label={contact.string.Contacts}
|
||||
selected={searchType === SearchType.Contacts}
|
||||
on:click={() => {
|
||||
searchType = SearchType.Contacts
|
||||
filters = []
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.browser {
|
||||
flex-grow: 2;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.bar {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
max-height: 4rem;
|
||||
}
|
||||
|
||||
.component {
|
||||
flex-grow: 2;
|
||||
height: 0;
|
||||
}
|
||||
</style>
|
@ -18,9 +18,9 @@
|
||||
import { getCurrentAccount } from '@anticrm/core'
|
||||
import { createQuery, getClient, CombineAvatars } from '@anticrm/presentation'
|
||||
import contact, { EmployeeAccount } from '@anticrm/contact'
|
||||
import { getCurrentLocation, navigate, SearchEdit, showPanel } from '@anticrm/ui'
|
||||
import { SearchEdit, showPanel } from '@anticrm/ui'
|
||||
import chunter from '../plugin'
|
||||
import { getDmName } from '../utils'
|
||||
import { getDmName, navigateToSpecial } from '../utils'
|
||||
import { userSearch } from '../index'
|
||||
|
||||
export let spaceId: Ref<DirectMessage> | undefined
|
||||
@ -72,9 +72,7 @@
|
||||
userSearch.set(ev.detail)
|
||||
|
||||
if (ev.detail !== '') {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = 'messagesBrowser'
|
||||
navigate(loc)
|
||||
navigateToSpecial('chunterBrowser')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -13,13 +13,15 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Asset } from '@anticrm/platform'
|
||||
import { AnySvelteComponent, getCurrentLocation, Icon, navigate, SearchEdit } from '@anticrm/ui'
|
||||
import type { Asset, IntlString } from '@anticrm/platform'
|
||||
import { AnySvelteComponent, Icon, Label, SearchEdit } from '@anticrm/ui'
|
||||
import { userSearch } from '../index'
|
||||
import { navigateToSpecial } from '../utils'
|
||||
|
||||
export let icon: Asset | AnySvelteComponent | undefined
|
||||
export let label: string
|
||||
export let description: string | undefined
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
export let label: string | undefined = undefined
|
||||
export let intlLabel: IntlString | undefined = undefined
|
||||
export let description: string | undefined = undefined
|
||||
|
||||
let userSearch_: string
|
||||
userSearch.subscribe((v) => (userSearch_ = v))
|
||||
@ -28,7 +30,13 @@
|
||||
<div class="ac-header__wrap-description">
|
||||
<div class="ac-header__wrap-title" on:click>
|
||||
{#if icon}<div class="ac-header__icon"><Icon {icon} size={'small'} /></div>{/if}
|
||||
<span class="ac-header__title">{label}</span>
|
||||
{#if label}
|
||||
<span class="ac-header__title">{label}</span>
|
||||
{:else if intlLabel}
|
||||
<div class="ac-header__title">
|
||||
<Label label={intlLabel} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if description}<span class="ac-header__description">{description}</span>{/if}
|
||||
</div>
|
||||
@ -38,9 +46,7 @@
|
||||
userSearch.set(ev.detail)
|
||||
|
||||
if (ev.detail !== '') {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = 'messagesBrowser'
|
||||
navigate(loc)
|
||||
navigateToSpecial('chunterBrowser')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -6,34 +6,34 @@
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Label, Scroller, SearchEdit } from '@anticrm/ui'
|
||||
import type { Filter } from '@anticrm/view'
|
||||
import { FilterBar, FilterButton } from '@anticrm/view-resources'
|
||||
import { FilterBar } from '@anticrm/view-resources'
|
||||
import MessageComponent from './Message.svelte'
|
||||
import { userSearch } from '../index'
|
||||
import plugin from '../plugin'
|
||||
import { openMessageFromSpecial } from '../utils'
|
||||
|
||||
let userSearch_: string = ''
|
||||
userSearch.subscribe((v) => (userSearch_ = v))
|
||||
export let withHeader: boolean = true
|
||||
|
||||
let searchQuery: DocumentQuery<ChunterMessage> = { $search: userSearch_ }
|
||||
export let search: string = ''
|
||||
|
||||
let filters: Filter[] = []
|
||||
let searchQuery: DocumentQuery<ChunterMessage> = { $search: search }
|
||||
|
||||
export let filters: Filter[] = []
|
||||
|
||||
function updateSearchQuery (search: string): void {
|
||||
searchQuery = { $search: search }
|
||||
}
|
||||
|
||||
$: updateSearchQuery(userSearch_)
|
||||
$: updateSearchQuery(search)
|
||||
|
||||
const client = getClient()
|
||||
const _class = chunter.class.ChunterMessage
|
||||
export let filterClass = chunter.class.ChunterMessage
|
||||
let messages: ChunterMessage[] = []
|
||||
|
||||
let resultQuery: DocumentQuery<ChunterMessage> = { ...searchQuery }
|
||||
|
||||
async function updateMessages (resultQuery: DocumentQuery<ChunterMessage>) {
|
||||
messages = await client.findAll(
|
||||
_class,
|
||||
filterClass,
|
||||
{
|
||||
...resultQuery
|
||||
},
|
||||
@ -97,32 +97,38 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="ac-header full divide">
|
||||
<div class="ac-header__wrap-title">
|
||||
<span class="ac-header__title"><Label label={plugin.string.MessagesBrowser} /></span>
|
||||
</div>
|
||||
<div class="ml-4"><FilterButton {_class} bind:filters /></div>
|
||||
<SearchEdit
|
||||
value={userSearch_}
|
||||
on:change={(ev) => {
|
||||
userSearch.set(ev.detail)
|
||||
updateSearchQuery(userSearch_)
|
||||
updateMessages(resultQuery)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<FilterBar {_class} query={searchQuery} bind:filters on:change={(e) => (resultQuery = e.detail)} />
|
||||
<Scroller>
|
||||
{#each messages as message}
|
||||
<div on:click={() => openMessageFromSpecial(message)}>
|
||||
<MessageComponent
|
||||
{message}
|
||||
{employees}
|
||||
on:openThread
|
||||
isPinned={pinnedIds.includes(message._id)}
|
||||
isSaved={savedMessagesIds.includes(message._id)}
|
||||
{savedAttachmentsIds}
|
||||
/>
|
||||
{#if withHeader}
|
||||
<div class="ac-header full divide">
|
||||
<div class="ac-header__wrap-title">
|
||||
<span class="ac-header__title"><Label label={plugin.string.MessagesBrowser} /></span>
|
||||
</div>
|
||||
{/each}
|
||||
</Scroller>
|
||||
<SearchEdit
|
||||
value={search}
|
||||
on:change={() => {
|
||||
updateSearchQuery(search)
|
||||
updateMessages(resultQuery)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<FilterBar _class={filterClass} query={searchQuery} bind:filters on:change={(e) => (resultQuery = e.detail)} />
|
||||
{#if messages.length > 0}
|
||||
<Scroller>
|
||||
{#each messages as message}
|
||||
<div on:click={() => openMessageFromSpecial(message)}>
|
||||
<MessageComponent
|
||||
{message}
|
||||
{employees}
|
||||
on:openThread
|
||||
isPinned={pinnedIds.includes(message._id)}
|
||||
isSaved={savedMessagesIds.includes(message._id)}
|
||||
{savedAttachmentsIds}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</Scroller>
|
||||
{:else}
|
||||
<div class="flex-center h-full text-lg">
|
||||
<Label label={plugin.string.NoResults} />
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -34,7 +34,7 @@ import CommentsPresenter from './components/CommentsPresenter.svelte'
|
||||
import CreateChannel from './components/CreateChannel.svelte'
|
||||
import CreateDirectMessage from './components/CreateDirectMessage.svelte'
|
||||
import EditChannel from './components/EditChannel.svelte'
|
||||
import MessagesBrowser from './components/MessagesBrowser.svelte'
|
||||
import ChunterBrowser from './components/ChunterBrowser.svelte'
|
||||
import ThreadView from './components/ThreadView.svelte'
|
||||
import Threads from './components/Threads.svelte'
|
||||
import SavedMessages from './components/SavedMessages.svelte'
|
||||
@ -176,7 +176,7 @@ export async function DeleteMessageFromSaved (message: ChunterMessage): Promise<
|
||||
|
||||
export const userSearch = writable('')
|
||||
|
||||
export function messageBrowserVisible (spaces: Space[]): boolean {
|
||||
export function chunterBrowserVisible (spaces: Space[]): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -186,21 +186,21 @@ export default async (): Promise<Resources> => ({
|
||||
CreateChannel,
|
||||
CreateDirectMessage,
|
||||
ChannelHeader,
|
||||
DmHeader,
|
||||
ChannelView,
|
||||
CommentPresenter,
|
||||
CommentsPresenter,
|
||||
ChannelPresenter,
|
||||
ChunterBrowser,
|
||||
DmHeader,
|
||||
DmPresenter,
|
||||
EditChannel,
|
||||
MessagesBrowser,
|
||||
Threads,
|
||||
ThreadView,
|
||||
SavedMessages
|
||||
},
|
||||
function: {
|
||||
GetDmName: getDmName,
|
||||
MessageBrowserVisible: messageBrowserVisible
|
||||
ChunterBrowserVisible: chunterBrowserVisible
|
||||
},
|
||||
activity: {
|
||||
TxCommentCreate,
|
||||
|
@ -82,6 +82,9 @@ export default mergeIds(chunterId, chunter, {
|
||||
ChannelBrowser: '' as IntlString,
|
||||
SavedItems: '' as IntlString,
|
||||
AddMembersHeader: '' as IntlString,
|
||||
MessagesBrowser: '' as IntlString
|
||||
MessagesBrowser: '' as IntlString,
|
||||
ChunterBrowser: '' as IntlString,
|
||||
Messages: '' as IntlString,
|
||||
NoResults: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -84,3 +84,16 @@ export function openMessageFromSpecial (message: ChunterMessage): void {
|
||||
}
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
export function navigateToSpecial (specialId: string): void {
|
||||
const loc = getCurrentLocation()
|
||||
loc.path[2] = specialId
|
||||
navigate(loc)
|
||||
}
|
||||
|
||||
export enum SearchType {
|
||||
Messages,
|
||||
Channels,
|
||||
Files,
|
||||
Contacts
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import contact, { Employee } from '@anticrm/contact'
|
||||
import { DocumentQuery, SortingOrder } from '@anticrm/core'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Scroller } from '@anticrm/ui'
|
||||
import EmployeePresenter from './EmployeePresenter.svelte'
|
||||
|
||||
export let search: string = ''
|
||||
const client = getClient()
|
||||
$: searchQuery = search.length ? { $search: search } : {}
|
||||
$: resultQuery = { ...searchQuery }
|
||||
let employees: Employee[] = []
|
||||
|
||||
async function updateEmployees (resultQuery: DocumentQuery<Employee>) {
|
||||
employees = await client.findAll(
|
||||
contact.class.Employee,
|
||||
{
|
||||
...resultQuery
|
||||
},
|
||||
{
|
||||
sort: { createOn: SortingOrder.Descending },
|
||||
limit: 100,
|
||||
lookup: { _id: { statuses: contact.class.Status } }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$: updateEmployees(resultQuery)
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<Scroller>
|
||||
<div>
|
||||
{#each employees as employee}
|
||||
<div class="fs-title item">
|
||||
<EmployeePresenter value={employee} avatarSize="medium" />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Scroller>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
.item {
|
||||
color: var(--caption-color);
|
||||
padding: 0.5rem 2rem;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--popup-bg-hover);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -13,6 +13,7 @@
|
||||
export let shouldShowName: boolean = true
|
||||
export let shouldShowPlaceholder = false
|
||||
export let onEmployeeEdit: ((event: MouseEvent) => void) | undefined = undefined
|
||||
export let avatarSize: 'inline' | 'tiny' | 'x-small' | 'small' | 'medium' | 'large' | 'x-large' = 'x-small'
|
||||
|
||||
let container: HTMLElement
|
||||
|
||||
@ -39,6 +40,7 @@
|
||||
onEdit={handlePersonEdit}
|
||||
{shouldShowAvatar}
|
||||
{shouldShowName}
|
||||
{avatarSize}
|
||||
{shouldShowPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
|
@ -36,6 +36,7 @@ import PersonPresenter from './components/PersonPresenter.svelte'
|
||||
import SocialEditor from './components/SocialEditor.svelte'
|
||||
import contact from './plugin'
|
||||
import EmployeePresenter from './components/EmployeePresenter.svelte'
|
||||
import EmployeeBrowser from './components/EmployeeBrowser.svelte'
|
||||
import EmployeeAccountPresenter from './components/EmployeeAccountPresenter.svelte'
|
||||
import OrganizationEditor from './components/OrganizationEditor.svelte'
|
||||
import PersonEditor from './components/PersonEditor.svelte'
|
||||
@ -53,6 +54,7 @@ export {
|
||||
OrganizationSelector,
|
||||
ChannelsDropdown,
|
||||
EmployeePresenter,
|
||||
EmployeeBrowser,
|
||||
MemberPresenter
|
||||
}
|
||||
|
||||
|
@ -71,10 +71,7 @@
|
||||
$: saveFilters(filters)
|
||||
|
||||
function saveFilters (filters: Filter[]) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.fragment = undefined
|
||||
loc.query = undefined
|
||||
const key = 'filter' + locationToUrl(loc)
|
||||
const key = makeKey(_class)
|
||||
if (filters.length > 0) {
|
||||
localStorage.setItem(key, JSON.stringify(filters))
|
||||
} else {
|
||||
@ -87,10 +84,7 @@
|
||||
function load (_class: Ref<Class<Doc>>) {
|
||||
loading = true
|
||||
const oldFilters = filters
|
||||
const loc = getCurrentLocation()
|
||||
loc.fragment = undefined
|
||||
loc.query = undefined
|
||||
const key = 'filter' + locationToUrl(loc)
|
||||
const key = makeKey(_class)
|
||||
const saved = localStorage.getItem(key)
|
||||
if (saved !== null) {
|
||||
filters = JSON.parse(saved)
|
||||
@ -101,6 +95,13 @@
|
||||
oldFilters.forEach((p) => p.onRemove?.())
|
||||
}
|
||||
|
||||
function makeKey (_class: Ref<Class<Doc>>): string {
|
||||
const loc = getCurrentLocation()
|
||||
loc.fragment = undefined
|
||||
loc.query = undefined
|
||||
return 'filter' + locationToUrl(loc) + _class
|
||||
}
|
||||
|
||||
async function makeQuery (query: DocumentQuery<Doc>, filters: Filter[]): Promise<void> {
|
||||
const newQuery = hierarchy.clone(query)
|
||||
for (let i = 0; i < filters.length; i++) {
|
||||
|
@ -20,7 +20,7 @@
|
||||
import view from '../../plugin'
|
||||
import FilterTypePopup from './FilterTypePopup.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let _class: Ref<Class<Doc>> | undefined
|
||||
export let filters: Filter[]
|
||||
|
||||
const client = getClient()
|
||||
@ -44,8 +44,13 @@
|
||||
)
|
||||
}
|
||||
|
||||
$: clazz = hierarchy.getClass(_class)
|
||||
$: visible = hierarchy.hasMixin(clazz, view.mixin.ClassFilters)
|
||||
let visible: boolean
|
||||
$: {
|
||||
if (_class) {
|
||||
const clazz = hierarchy.getClass(_class)
|
||||
visible = hierarchy.hasMixin(clazz, view.mixin.ClassFilters)
|
||||
} else visible = false
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if visible}
|
||||
|
@ -143,7 +143,7 @@
|
||||
<div class="check pointer-events-none">
|
||||
<CheckBox checked={isSelected(value, selectedValues)} primary />
|
||||
</div>
|
||||
{#if value}
|
||||
{#if value !== undefined}
|
||||
<svelte:component
|
||||
this={attribute.presenter}
|
||||
value={typeof value === 'string' ? realValue : value}
|
||||
|
@ -13,7 +13,16 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, FindOptions, getCurrentAccount, Ref, SortingOrder, SortingQuery, Space } from '@anticrm/core'
|
||||
import {
|
||||
Class,
|
||||
DocumentQuery,
|
||||
FindOptions,
|
||||
getCurrentAccount,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
SortingQuery,
|
||||
Space
|
||||
} from '@anticrm/core'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
@ -27,35 +36,44 @@
|
||||
} from '@anticrm/ui'
|
||||
import presentation, { createQuery, getClient } from '@anticrm/presentation'
|
||||
import plugin from '../plugin'
|
||||
import { SpacePresenter } from '@anticrm/view-resources'
|
||||
import { FilterBar, FilterButton, SpacePresenter } from '@anticrm/view-resources'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { classIcon } from '../utils'
|
||||
import { Filter } from '@anticrm/view'
|
||||
|
||||
export let _class: Ref<Class<Space>>
|
||||
export let label: IntlString
|
||||
export let createItemDialog: AnyComponent | undefined
|
||||
export let createItemLabel: IntlString = presentation.string.Create
|
||||
export let withHeader: boolean = true
|
||||
export let withFilterButton: boolean = true
|
||||
|
||||
const me = getCurrentAccount()._id
|
||||
const client = getClient()
|
||||
const spaceQuery = createQuery()
|
||||
let search: string = ''
|
||||
export let search: string = ''
|
||||
const sort: SortingQuery<Space> = {
|
||||
name: SortingOrder.Ascending
|
||||
}
|
||||
export let filters: Filter[] = []
|
||||
let searchQuery: DocumentQuery<Space>
|
||||
let resultQuery: DocumentQuery<Space>
|
||||
|
||||
let spaces: Space[] = []
|
||||
|
||||
$: update(search, sort)
|
||||
$: updateSearchQuery(search)
|
||||
$: update(sort, resultQuery)
|
||||
|
||||
async function update (search: string, sort: SortingQuery<Space>): Promise<void> {
|
||||
const query = search.trim().length > 0 ? { name: { $like: '%' + search + '%' } } : {}
|
||||
async function update (sort: SortingQuery<Space>, resultQuery: DocumentQuery<Space>): Promise<void> {
|
||||
const options: FindOptions<Space> = {
|
||||
sort
|
||||
}
|
||||
|
||||
spaceQuery.query(
|
||||
_class,
|
||||
query,
|
||||
{
|
||||
...resultQuery
|
||||
},
|
||||
(res) => {
|
||||
spaces = res.filter((p) => !p.private || p.members.includes(me))
|
||||
},
|
||||
@ -63,6 +81,10 @@
|
||||
)
|
||||
}
|
||||
|
||||
function updateSearchQuery (search: string): void {
|
||||
searchQuery = search.length ? { $search: search } : {}
|
||||
}
|
||||
|
||||
function showCreateDialog (ev: Event) {
|
||||
showPopup(createItemDialog as AnyComponent, {}, 'middle')
|
||||
}
|
||||
@ -92,15 +114,31 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="ac-header full divide">
|
||||
<div class="ac-header__wrap-title">
|
||||
<span class="ac-header__title"><Label {label} /></span>
|
||||
{#if withHeader}
|
||||
<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>
|
||||
{#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>
|
||||
<div class="ml-8 mr-8 mt-4 mb-4">
|
||||
<SearchEdit
|
||||
bind:value={search}
|
||||
on:change={(ev) => {
|
||||
updateSearchQuery(search)
|
||||
update(sort, resultQuery)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if withFilterButton}
|
||||
<div class="ml-10 mt-4 mb-4">
|
||||
<FilterButton {_class} bind:filters />
|
||||
</div>
|
||||
{/if}
|
||||
<FilterBar {_class} query={searchQuery} bind:filters on:change={(e) => (resultQuery = e.detail)} />
|
||||
<Scroller padding={'2.5rem'}>
|
||||
<div class="flex-col">
|
||||
{#each spaces as space (space._id)}
|
||||
@ -137,9 +175,11 @@
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex-center mt-10">
|
||||
<Button size={'x-large'} kind={'primary'} label={createItemLabel} on:click={(ev) => showCreateDialog(ev)} />
|
||||
</div>
|
||||
{#if createItemDialog}
|
||||
<div class="flex-center mt-10">
|
||||
<Button size={'x-large'} kind={'primary'} label={createItemLabel} on:click={(ev) => showCreateDialog(ev)} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Scroller>
|
||||
|
||||
|
@ -26,7 +26,7 @@ import { doNavigate } from './utils'
|
||||
function hasArchiveSpaces (spaces: Space[]): boolean {
|
||||
return spaces.find((sp) => sp.archived) !== undefined
|
||||
}
|
||||
|
||||
export { default as SpaceBrowser } from './components/SpaceBrowser.svelte'
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
WorkbenchApp,
|
||||
|
Loading…
Reference in New Issue
Block a user