Sortable list (#2618)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2023-02-10 23:45:14 +07:00
committed by GitHub
parent df7667289d
commit f7e495b609
16 changed files with 420 additions and 415 deletions
@@ -1,6 +1,6 @@
<script lang="ts">
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
import { Issue } from '@hcengineering/tracker'
import { Component } from '@hcengineering/ui'
import { Viewlet, ViewOptions } from '@hcengineering/view'
import tracker from '../../plugin'
@@ -10,9 +10,6 @@
export let query: DocumentQuery<Issue> = {}
export let space: Ref<Space> | undefined
// Extra properties
export let teams: Map<Ref<Team>, Team> | undefined
export let issueStatuses: Map<Ref<Team>, WithLookup<IssueStatus>[]>
export let viewOptions: ViewOptions
const createItemDialog = CreateIssue
@@ -32,8 +29,7 @@
viewOptions,
viewOptionsConfig: viewlet.viewOptions?.other,
space,
query,
props: { teams, issueStatuses }
query
}}
/>
{/if}
@@ -1,232 +0,0 @@
<!--
// 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 { createEventDispatcher } from 'svelte'
import { getObjectValue, WithLookup } from '@hcengineering/core'
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
import notification from '@hcengineering/notification'
import { tooltip, CheckBox, Component, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view'
import tracker from '../../plugin'
import { IssuesGroupByKeys } from '../../utils'
import { FixedColumn } from '@hcengineering/view-resources'
import Circles from '../icons/Circles.svelte'
export let use: HTMLElement
export let docObject: Issue
export let model: AttributeModel[]
export let groupByKey: IssuesGroupByKeys | undefined
export let checked: boolean
export let selected: boolean
export let statuses: WithLookup<IssueStatus>[]
export let currentTeam: Team | undefined
const dispatch = createEventDispatcher()
$: compactMode = $deviceInfo.twoRows
</script>
<div
bind:this={use}
class="listGrid antiList__row row gap-2 flex-grow"
class:checking={checked}
class:mListGridFixed={selected}
class:mListGridSelected={selected}
on:contextmenu
on:focus
on:mouseover
>
<div class="flex-center relative" use:tooltip={{ label: tracker.string.SelectIssue, direction: 'bottom' }}>
<div class="antiList-cells__notifyCell">
<div class="antiList-cells__checkCell">
<CheckBox
{checked}
on:value={(event) => {
dispatch('check', { docs: [docObject], value: event.detail })
}}
/>
</div>
<Component
is={notification.component.NotificationPresenter}
showLoading={false}
props={{ value: docObject, kind: 'table' }}
/>
</div>
</div>
{#each model as attributeModel}
{#if attributeModel.props?.type === 'grow'}
<svelte:component this={attributeModel.presenter} />
{:else if (!groupByKey || attributeModel.props?.excludeByKey !== groupByKey) && !(attributeModel.props?.optional && compactMode)}
{#if attributeModel.props?.fixed}
<FixedColumn key={`issue_${attributeModel.key}`} justify={attributeModel.props.fixed}>
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
groupBy={groupByKey}
{...attributeModel.props}
{statuses}
{currentTeam}
/>
</FixedColumn>
{:else}
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
issueId={docObject._id}
groupBy={groupByKey}
{...attributeModel.props}
{statuses}
{currentTeam}
/>
{/if}
{/if}
{/each}
{#if compactMode}
<div class="panel-trigger" tabindex="-1">
<Circles />
<div class="space" />
<Circles />
</div>
<div class="hidden-panel" tabindex="-1">
<div class="header">
<Circles />
<div class="space" />
<Circles />
</div>
<div class="scroll-box gap-2">
{#each model as attributeModel}
{@const value = getObjectValue(attributeModel.key, docObject)}
{#if attributeModel.props?.optional && attributeModel.props?.excludeByKey !== groupByKey && value !== undefined}
<svelte:component
this={attributeModel.presenter}
value={value ?? ''}
issueId={docObject._id}
groupBy={groupByKey}
{...attributeModel.props}
{statuses}
{currentTeam}
/>
{/if}
{/each}
</div>
</div>
{/if}
</div>
<style lang="scss">
.row:not(:last-child) {
border-bottom: 1px solid var(--accent-bg-color);
}
.listGrid {
position: relative;
display: flex;
align-items: center;
padding: 0 0.75rem 0 0.875rem;
width: 100%;
height: 2.75rem;
min-height: 2.75rem;
color: var(--theme-caption-color);
&.checking {
background-color: var(--highlight-select);
border-bottom-color: var(--highlight-select);
&:hover {
background-color: var(--highlight-select-hover);
border-bottom-color: var(--highlight-select-hover);
}
}
&.mListGridSelected {
background-color: var(--highlight-hover);
}
.hidden-panel,
.panel-trigger {
position: absolute;
display: flex;
align-items: center;
top: 0;
bottom: 0;
height: 100%;
}
.hidden-panel {
overflow: hidden;
right: 0;
width: 80%;
background-color: var(--accent-bg-color);
opacity: 0;
pointer-events: none;
z-index: 2;
transition-property: opacity, width;
transition-duration: 0.15s;
transition-timing-function: var(--timing-main);
.header {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 0.25rem;
width: 0.375rem;
min-width: 0.375rem;
height: 100%;
opacity: 0.25;
}
.scroll-box {
overflow: auto visible;
display: flex;
align-items: center;
margin: 0.125rem 0.25rem 0;
padding: 0.25rem 0.25rem;
min-width: 0;
&::-webkit-scrollbar:horizontal {
height: 3px;
}
}
}
.panel-trigger {
flex-direction: column;
justify-content: center;
padding: 0 0.125rem;
right: 2.5rem;
width: 0.75rem;
border: 1px solid transparent;
border-radius: 0.25rem;
opacity: 0.1;
z-index: 1;
transition: opacity 0.15s var(--timing-main);
&:focus {
border-color: var(--primary-edit-border-color);
opacity: 0.25;
}
& > * {
pointer-events: none;
}
}
.hidden-panel:focus-within,
.hidden-panel:focus,
.panel-trigger:focus + .hidden-panel {
width: 100%;
opacity: 1;
pointer-events: all;
}
.space {
min-height: 0.1075rem;
}
}
</style>
@@ -1,8 +1,8 @@
<script lang="ts">
import { DocumentQuery, Ref, SortingOrder, Space, toIdMap, WithLookup } from '@hcengineering/core'
import { DocumentQuery, Ref, Space, WithLookup } from '@hcengineering/core'
import { IntlString, translate } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Issue, IssueStatus, Team } from '@hcengineering/tracker'
import { getClient } from '@hcengineering/presentation'
import { Issue } from '@hcengineering/tracker'
import { Button, IconDetails, IconDetailsFilled } from '@hcengineering/ui'
import view, { Viewlet } from '@hcengineering/view'
import { FilterBar, getActiveViewletId, getViewOptions, setActiveViewletId } from '@hcengineering/view-resources'
@@ -65,33 +65,6 @@
$: if (docWidth <= 900 && !docSize) docSize = true
$: if (docWidth > 900 && docSize) docSize = false
const teamQuery = createQuery()
let _teams: Map<Ref<Team>, Team> | undefined = undefined
$: teamQuery.query(tracker.class.Team, {}, (result) => {
_teams = toIdMap(result)
})
let issueStatuses: Map<Ref<Team>, WithLookup<IssueStatus>[]>
const statusesQuery = createQuery()
statusesQuery.query(
tracker.class.IssueStatus,
{},
(statuses) => {
const st = new Map<Ref<Team>, WithLookup<IssueStatus>[]>()
for (const s of statuses) {
const id = s.attachedTo as Ref<Team>
st.set(id, [...(st.get(id) ?? []), s])
}
issueStatuses = st
},
{
lookup: { category: tracker.class.IssueStatusCategory },
sort: { rank: SortingOrder.Ascending }
}
)
$: viewOptions = getViewOptions(viewlet)
</script>
@@ -120,8 +93,8 @@
<slot name="afterHeader" />
<FilterBar _class={tracker.class.Issue} query={searchQuery} {viewOptions} on:change={(e) => (resultQuery = e.detail)} />
<div class="flex w-full h-full clear-mins">
{#if viewlet && _teams && issueStatuses}
<IssuesContent {viewlet} query={resultQuery} {space} teams={_teams} {issueStatuses} {viewOptions} />
{#if viewlet}
<IssuesContent {viewlet} query={resultQuery} {space} {viewOptions} />
{/if}
{#if $$slots.aside !== undefined && asideShown}
<div class="popupPanel-body__aside flex" class:float={asideFloat} class:shown={asideShown}>
@@ -32,7 +32,7 @@
import { subIssueListProvider } from '../../../utils'
export let value: WithLookup<Issue>
export let currentTeam: Team | undefined
export let currentTeam: Team | undefined = undefined
export let kind: ButtonKind = 'link-bordered'
export let size: ButtonSize = 'inline'
@@ -41,9 +41,23 @@
let btn: HTMLElement
$: team = currentTeam
let subIssues: Issue[] = []
let countComplate: number = 0
const teamQuery = createQuery()
$: if (currentTeam === undefined) {
teamQuery.query(
tracker.class.Team,
{
_id: value.space
},
(res) => ([team] = res)
)
} else {
teamQuery.unsubscribe()
}
const query = createQuery()
const statusesQuery = createQuery()
@@ -103,7 +117,7 @@
SelectPopup,
{
value: subIssues.map((iss) => {
const text = currentTeam ? `${getIssueId(currentTeam, iss)} ${iss.title}` : iss.title
const text = team ? `${getIssueId(team, iss)} ${iss.title}` : iss.title
return { id: iss._id, text, isSelected: iss._id === value._id, ...getIssueStatusIcon(iss, statuses) }
}),
@@ -31,7 +31,7 @@
export let size: ButtonSize = 'large'
export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = undefined
export let currentTeam: Team | undefined
export let currentTeam: Team | undefined = undefined
const client = getClient()
const dispatch = createEventDispatcher()
@@ -175,7 +175,9 @@
size: 'small',
shape: 'round',
shouldShowPlaceholder: false,
excludeByKey: 'sprint',
listProps: {
excludeByKey: 'sprint'
},
isEditable: false
}
},