mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-09 11:17:43 +02:00
Sortable list (#2618)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -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
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user