mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 19:44:59 +03:00
Kanban search (#714)
Signed-off-by: Ilya Sumbatyants <ilya.sumb@gmail.com>
This commit is contained in:
parent
8be72b58aa
commit
4214d62da6
@ -79,7 +79,7 @@ export function setClient (_client: Client): void {
|
||||
}
|
||||
|
||||
export class LiveQuery {
|
||||
private unsubscribe = () => {}
|
||||
unsubscribe = () => {}
|
||||
|
||||
constructor () {
|
||||
onDestroy(() => {
|
||||
@ -95,7 +95,11 @@ export class LiveQuery {
|
||||
options?: FindOptions<T>
|
||||
): void {
|
||||
this.unsubscribe()
|
||||
this.unsubscribe = liveQuery.query(_class, query, callback, options)
|
||||
const unsub = liveQuery.query(_class, query, callback, options)
|
||||
this.unsubscribe = () => {
|
||||
unsub()
|
||||
this.unsubscribe = () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
export let _class: Ref<Class<Item>>
|
||||
export let space: Ref<SpaceWithStates>
|
||||
export let open: AnyComponent
|
||||
export let search: string
|
||||
export let options: FindOptions<Item> | undefined
|
||||
export let config: string[]
|
||||
|
||||
@ -55,19 +56,51 @@
|
||||
|
||||
const doneStatesQ = createQuery()
|
||||
$: if (kanban !== undefined) {
|
||||
doneStatesQ.query(task.class.DoneState, { space: kanban.space }, (result) => {
|
||||
doneStatesQ.query(
|
||||
task.class.DoneState,
|
||||
{ space: kanban.space, ...search !== '' ? {$search: search} : {} },
|
||||
(result) => {
|
||||
wonState = result.find((x) => x._class === task.class.WonState)
|
||||
lostState = result.find((x) => x._class === task.class.LostState)
|
||||
})
|
||||
}
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(_class, { space, doneState: null }, result => { objects = result }, {
|
||||
const objsQ = createQuery()
|
||||
$: objsQ.query(
|
||||
_class,
|
||||
{
|
||||
space,
|
||||
doneState: null,
|
||||
...search !== '' ? {$search: search} : {}
|
||||
},
|
||||
result => { objects = result },
|
||||
{
|
||||
...options,
|
||||
sort: {
|
||||
rank: SortingOrder.Ascending
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const filteredObjsQ = createQuery()
|
||||
|
||||
// Undefined means no filtering
|
||||
let target: Set<Ref<Doc>> | undefined
|
||||
$: if (search === '') {
|
||||
filteredObjsQ.unsubscribe()
|
||||
target = undefined
|
||||
} else {
|
||||
filteredObjsQ.query(
|
||||
_class,
|
||||
{
|
||||
space,
|
||||
doneState: null,
|
||||
...search !== '' ? {$search: search} : {}
|
||||
},
|
||||
result => { target = new Set(result.map(x => x._id)) },
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
function dragover (ev: MouseEvent, object: Item) {
|
||||
if (dragCard !== object) {
|
||||
@ -162,7 +195,7 @@
|
||||
}}>
|
||||
<!-- <KanbanCardEmpty label={'Create new application'} /> -->
|
||||
{#each objects as object, j (object)}
|
||||
{#if object.state === state._id}
|
||||
{#if object.state === state._id && (target === undefined || target.has(object._id))}
|
||||
<div
|
||||
class="step-tb75"
|
||||
on:dragover|preventDefault={(ev) => {
|
||||
|
@ -0,0 +1,70 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 type { Ref, Class, Doc, Space, WithLookup } from '@anticrm/core'
|
||||
import type { Viewlet } from '@anticrm/view'
|
||||
import { Component } from '@anticrm/ui'
|
||||
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let space: Ref<Space>
|
||||
export let search: string
|
||||
export let viewlet: WithLookup<Viewlet> | undefined
|
||||
</script>
|
||||
|
||||
{#if viewlet}
|
||||
<div class="container">
|
||||
<Component is={viewlet.$lookup?.descriptor?.component} props={ {
|
||||
_class,
|
||||
space,
|
||||
open: viewlet.open,
|
||||
options: viewlet.options,
|
||||
config: viewlet.config,
|
||||
search
|
||||
} } />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.toolbar {
|
||||
margin: 1.25rem 1.75rem 1.75rem 2.5rem;
|
||||
|
||||
.btn {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
background-color: transparent;
|
||||
border-radius: .5rem;
|
||||
cursor: pointer;
|
||||
|
||||
color: var(--theme-content-trans-color);
|
||||
&:hover { color: var(--theme-caption-color); }
|
||||
&.selected {
|
||||
color: var(--theme-content-accent-color);
|
||||
background-color: var(--theme-button-bg-enabled);
|
||||
cursor: default;
|
||||
&:hover { color: var(--theme-caption-color); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
@ -14,42 +14,78 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import core, { Class, Doc, WithLookup } from '@anticrm/core'
|
||||
import type { Ref, Space } from '@anticrm/core'
|
||||
import { Icon, ActionIcon, Button, IconMoreH, IconAdd } from '@anticrm/ui'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import Header from './Header.svelte'
|
||||
import Star from './icons/Star.svelte'
|
||||
|
||||
import { getClient, createQuery } from '@anticrm/presentation'
|
||||
import { Icon, Button, EditWithIcon, IconSearch, Tooltip } from '@anticrm/ui'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import view, { Viewlet } from '@anticrm/view'
|
||||
|
||||
import { classIcon } from '../utils'
|
||||
import core from '@anticrm/core'
|
||||
import workbench from '../plugin'
|
||||
|
||||
export let space: Ref<Space> | undefined
|
||||
import Header from './Header.svelte'
|
||||
|
||||
export let spaceId: Ref<Space> | undefined
|
||||
export let _class: Ref<Class<Doc>> | undefined
|
||||
export let createItemDialog: AnyComponent | undefined
|
||||
export let divider: boolean = false
|
||||
export let search: string
|
||||
export let viewlet: WithLookup<Viewlet> | undefined
|
||||
|
||||
const client = getClient()
|
||||
const query = createQuery()
|
||||
let data: Space | undefined
|
||||
let space: Space | undefined
|
||||
|
||||
$: query.query(core.class.Space, { _id: space }, result => { data = result[0] })
|
||||
$: query.query(core.class.Space, { _id: spaceId }, result => { space = result[0] })
|
||||
|
||||
function onSearch(ev: Event) {
|
||||
search = (ev.target as HTMLInputElement).value
|
||||
}
|
||||
|
||||
let viewlets: WithLookup<Viewlet>[] = []
|
||||
async function getViewlets(attachTo: Ref<Class<Doc>>): Promise<void> {
|
||||
viewlets = await client.findAll(view.class.Viewlet, { attachTo }, { lookup: {
|
||||
descriptor: core.class.Class
|
||||
}})
|
||||
}
|
||||
|
||||
$: if (_class) {
|
||||
getViewlets(_class)
|
||||
}
|
||||
|
||||
function resetSelectedViewlet (_space: Ref<Space> | undefined) {
|
||||
selectedViewlet = 0
|
||||
}
|
||||
|
||||
$: resetSelectedViewlet(spaceId)
|
||||
|
||||
function showCreateDialog(ev: Event) {
|
||||
showPopup(createItemDialog as AnyComponent, { space }, ev.target as HTMLElement)
|
||||
showPopup(createItemDialog as AnyComponent, { space: spaceId }, ev.target as HTMLElement)
|
||||
}
|
||||
|
||||
let selectedViewlet = 0
|
||||
$: viewlet = viewlets[selectedViewlet]
|
||||
</script>
|
||||
|
||||
<div class="spaceheader-container" class:bottom-divider={divider}>
|
||||
{#if data}
|
||||
<Header icon={classIcon(client, data._class)} label={data.name} description={data.description} />
|
||||
<div class="spaceheader-container">
|
||||
{#if space}
|
||||
<Header icon={classIcon(client, space._class)} label={space.name} description={space.description} />
|
||||
{#if viewlets.length > 1}
|
||||
<div class="flex">
|
||||
{#each viewlets as viewlet, i}
|
||||
<Tooltip label={viewlet.$lookup?.descriptor?.label} direction={'top'}>
|
||||
<div class="flex-center btn" class:selected={selectedViewlet === i} on:click={()=>{ selectedViewlet = i }}>
|
||||
<Icon icon={viewlet.$lookup?.descriptor?.icon} size={'small'}/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<EditWithIcon icon={IconSearch} placeholder={'Search'} on:change={onSearch}/>
|
||||
{#if createItemDialog}
|
||||
<Button label={workbench.string.Create} primary={true} size={'small'} on:click={(ev) => showCreateDialog(ev)}/>
|
||||
{/if}
|
||||
<!-- <ActionIcon label={'Favorite'} icon={Star} size={'small'}/>
|
||||
<ActionIcon label={'Create'} icon={IconAdd} size={'small'}/>
|
||||
<ActionIcon label={'More...'} icon={IconMoreH} size={'small'}/> -->
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -65,4 +101,21 @@
|
||||
height: 4rem;
|
||||
min-height: 4rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
background-color: transparent;
|
||||
border-radius: .5rem;
|
||||
cursor: pointer;
|
||||
|
||||
color: var(--theme-content-trans-color);
|
||||
&:hover { color: var(--theme-caption-color); }
|
||||
&.selected {
|
||||
color: var(--theme-content-accent-color);
|
||||
background-color: var(--theme-button-bg-enabled);
|
||||
cursor: default;
|
||||
&:hover { color: var(--theme-caption-color); }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,5 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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
|
||||
@ -15,106 +14,30 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Ref, Space, WithLookup } from '@anticrm/core'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Viewlet } from '@anticrm/view'
|
||||
import type { ViewConfiguration } from '@anticrm/workbench'
|
||||
|
||||
import type { Ref, Class ,Doc, FindOptions, Space, WithLookup, Obj, Client } from '@anticrm/core'
|
||||
import type { Viewlet } from '@anticrm/view'
|
||||
import SpaceContent from './SpaceContent.svelte'
|
||||
import SpaceHeader from './SpaceHeader.svelte'
|
||||
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
export let currentSpace: Ref<Space> | undefined
|
||||
export let currentView: ViewConfiguration | undefined
|
||||
export let createItemDialog: AnyComponent | undefined
|
||||
|
||||
import { Icon, Component, EditWithIcon, IconSearch, Tooltip } from '@anticrm/ui'
|
||||
let search: string = ''
|
||||
let viewlet: WithLookup<Viewlet> | undefined = undefined
|
||||
|
||||
import view from '@anticrm/view'
|
||||
import core from '@anticrm/core'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let space: Ref<Space>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
type ViewletConfig = WithLookup<Viewlet>
|
||||
|
||||
async function getViewlets(client: Client, _class: Ref<Class<Obj>>): Promise<ViewletConfig[]> {
|
||||
return await client.findAll(view.class.Viewlet, { attachTo: _class }, { lookup: {
|
||||
descriptor: core.class.Class
|
||||
}})
|
||||
}
|
||||
|
||||
let selected = 0
|
||||
|
||||
function onSpace(space: Ref<Space>) {
|
||||
selected = 0
|
||||
}
|
||||
|
||||
$: onSpace(space)
|
||||
|
||||
let search = ''
|
||||
|
||||
function onSearch(ev: Event) {
|
||||
search = (ev.target as HTMLInputElement).value
|
||||
}
|
||||
function resetSearch (_space: Ref<Space> | undefined): void {
|
||||
search = ''
|
||||
}
|
||||
|
||||
$: resetSearch(currentSpace)
|
||||
</script>
|
||||
|
||||
{#await getViewlets(client, _class)}
|
||||
...
|
||||
{:then viewlets}
|
||||
|
||||
{#if viewlets.length > 0}
|
||||
<div class="flex-between toolbar">
|
||||
<EditWithIcon icon={IconSearch} placeholder={'Search'} on:change={onSearch}/>
|
||||
|
||||
{#if viewlets.length > 1}
|
||||
<div class="flex">
|
||||
{#each viewlets as viewlet, i}
|
||||
<Tooltip label={viewlet.$lookup?.descriptor?.label} direction={'top'}>
|
||||
<div class="flex-center btn" class:selected={selected === i} on:click={()=>{ selected = i }}>
|
||||
<Icon icon={viewlet.$lookup?.descriptor?.icon} size={'small'}/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="container">
|
||||
<Component is={viewlets[selected].$lookup?.descriptor?.component} props={ {
|
||||
_class,
|
||||
space,
|
||||
open: viewlets[selected].open,
|
||||
options: viewlets[selected].options,
|
||||
config: viewlets[selected].config,
|
||||
search
|
||||
} } />
|
||||
</div>
|
||||
|
||||
{/await}
|
||||
|
||||
<style lang="scss">
|
||||
.toolbar {
|
||||
margin: 1.25rem 1.75rem 1.75rem 2.5rem;
|
||||
|
||||
.btn {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
background-color: transparent;
|
||||
border-radius: .5rem;
|
||||
cursor: pointer;
|
||||
|
||||
color: var(--theme-content-trans-color);
|
||||
&:hover { color: var(--theme-caption-color); }
|
||||
&.selected {
|
||||
color: var(--theme-content-accent-color);
|
||||
background-color: var(--theme-button-bg-enabled);
|
||||
cursor: default;
|
||||
&:hover { color: var(--theme-caption-color); }
|
||||
}
|
||||
}
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<SpaceHeader spaceId={currentSpace} _class={currentView?.class} {createItemDialog} bind:search={search} bind:viewlet={viewlet} />
|
||||
{#if currentView && currentSpace}
|
||||
<SpaceContent space={currentSpace} _class={currentView.class} {search} {viewlet} />
|
||||
{/if}
|
||||
|
@ -26,7 +26,6 @@
|
||||
import workbench from '../plugin'
|
||||
|
||||
import Navigator from './Navigator.svelte'
|
||||
import SpaceHeader from './SpaceHeader.svelte'
|
||||
import SpaceView from './SpaceView.svelte'
|
||||
|
||||
import { AnyComponent, Component, location, Popup, showPopup, TooltipInstance, closeTooltip, ActionIcon, IconEdit, AnySvelteComponent } from '@anticrm/ui'
|
||||
@ -141,10 +140,7 @@
|
||||
{:else if specialComponent}
|
||||
<Component is={specialComponent} />
|
||||
{:else}
|
||||
<SpaceHeader space={currentSpace} {createItemDialog} />
|
||||
{#if currentView && currentSpace}
|
||||
<SpaceView space={currentSpace} _class={currentView.class} options={currentView.options} />
|
||||
{/if}
|
||||
<SpaceView {currentSpace} {currentView} {createItemDialog}/>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- <div class="aside"><Chat thread/></div> -->
|
||||
|
Loading…
Reference in New Issue
Block a user