Members init (#1479)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov
2022-04-21 22:47:04 +07:00
committed by GitHub
parent c1a697ee5e
commit ed0a747330
31 changed files with 624 additions and 186 deletions
@@ -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} />
&#183
{/if}
{space.members.length}
&#183
{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)}>