mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-22 09:35:00 +02:00
Members init (#1479)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
@@ -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}
|
||||
{#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>
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
Reference in New Issue
Block a user