Add horizontal view to the sortable list (#2412)

Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@xored.com>
This commit is contained in:
Sergei Ogorelkov 2022-12-02 15:30:51 +06:00 committed by GitHub
parent 31c7586ddf
commit 3d53ebc8b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 106 additions and 128 deletions

View File

@ -21,7 +21,6 @@
import view, { AttributeModel, ObjectFactory } from '@hcengineering/view'
import { flip } from 'svelte/animate'
import { getObjectPresenter } from '../../utils'
import SortableListItem from './SortableListItem.svelte'
/*
How to use:
@ -32,7 +31,6 @@
To create a new items, we should add "ObjectFactory" mixin also.
We can create a custom list items or editor based on "SortableListItem"
and "SortableListItemPresenter"
Important: the "ObjectFactory" component must emit the "close" event
*/
@ -42,6 +40,7 @@
export let query: DocumentQuery<Doc> = {}
export let queryOptions: FindOptions<Doc> | undefined = undefined
export let presenterProps: Record<string, any> = {}
export let direction: 'row' | 'column' = 'column'
export let flipDuration = 200
const client = getClient()
@ -169,13 +168,16 @@
{#if isLoading}
<Loading />
{:else if model && items}
<div class="flex-col flex-gap-1">
{@const isVertical = direction === 'column'}
<div class="flex-gap-1" class:flex-col={isVertical} class:flex={!isVertical} class:flex-wrap={!isVertical}>
{#each items as item, index (item._id)}
{@const isDraggable = isSortable && items.length > 1 && !areItemsSorting}
<div
class="row"
class:is-dragged-over-up={draggingIndex !== null && index === hoveringIndex && index < draggingIndex}
class:is-dragged-over-down={draggingIndex !== null && index === hoveringIndex && index > draggingIndex}
class="item"
class:column={isVertical}
class:row={!isVertical}
class:is-dragged-over-before={draggingIndex !== null && index === hoveringIndex && index < draggingIndex}
class:is-dragged-over-after={draggingIndex !== null && index === hoveringIndex && index > draggingIndex}
draggable={isDraggable}
animate:flip={{ duration: flipDuration }}
on:dragstart={(ev) => handleDragStart(ev, index)}
@ -183,9 +185,7 @@
on:drop={() => handleDrop(index)}
on:dragend={resetDrag}
>
<SortableListItem {isDraggable}>
<svelte:component this={model.presenter} {...model.props ?? {}} value={item} />
</SortableListItem>
<svelte:component this={model.presenter} {isDraggable} {...model.props ?? {}} value={item} />
</div>
{/each}
@ -198,22 +198,27 @@
</div>
<style lang="scss">
.row {
.item {
position: relative;
overflow: hidden;
&.is-dragged-over-up::before {
&.is-dragged-over-before::before,
&.is-dragged-over-after::before {
position: absolute;
content: '';
inset: 0;
border-top: 1px solid var(--theme-bg-check);
}
&.is-dragged-over-down::before {
position: absolute;
content: '';
inset: 0;
&.column.is-dragged-over-before::before {
border-top: 1px solid var(--theme-bg-check);
}
&.column.is-dragged-over-after::before {
border-bottom: 1px solid var(--theme-bg-check);
}
&.row.is-dragged-over-before::before {
border-left: 1px solid var(--theme-bg-check);
}
&.row.is-dragged-over-after::before {
border-right: 1px solid var(--theme-bg-check);
}
}
</style>

View File

@ -13,22 +13,81 @@
// limitations under the License.
-->
<script lang="ts">
import presentation from '@hcengineering/presentation'
import { Icon, IconEdit, IconClose, tooltip, Button } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import Circles from '../icons/Circles.svelte'
export let isDraggable = false
export let isEditable = false
export let isDeletable = false
export let isEditing = false
export let isSaving = false
export let canSave = false
const dispatch = createEventDispatcher()
$: areButtonsVisible = isEditable || isDeletable || isEditing
</script>
<div class="root flex background-button-bg-color border-radius-1">
<div
class="root flex background-button-bg-color border-radius-1"
on:dblclick|preventDefault={isEditable && !isEditing ? () => dispatch('edit') : undefined}
>
<div class="flex-center ml-2">
<div class="flex-no-shrink circles-mark" class:isDraggable><Circles /></div>
</div>
<div class="root flex flex-between items-center w-full p-2">
<div class="content w-full">
<slot />
</div>
{#if areButtonsVisible}
<div class="ml-auto pl-2 buttons-group small-gap flex-no-shrink">
{#if isEditing}
<Button label={presentation.string.Cancel} kind="secondary" on:click={() => dispatch('cancel')} />
<Button
label={presentation.string.Save}
kind="primary"
loading={isSaving}
disabled={!canSave}
on:click={() => dispatch('save')}
/>
{:else}
{#if isEditable}
<button
class="btn"
use:tooltip={{ label: presentation.string.Edit }}
on:click|preventDefault={() => dispatch('edit')}
>
<Icon icon={IconEdit} size="small" />
</button>
{/if}
{#if isDeletable}
<button
class="btn"
use:tooltip={{ label: presentation.string.Remove }}
on:click|preventDefault={() => dispatch('delete')}
>
<Icon icon={IconClose} size="small" />
</button>
{/if}
{/if}
</div>
{/if}
</div>
</div>
<style lang="scss">
.root {
overflow: hidden;
&:hover {
.btn {
opacity: 1;
}
.circles-mark.isDraggable {
cursor: grab;
opacity: 0.4;
@ -36,6 +95,28 @@
}
}
.content {
overflow: hidden;
}
.btn {
position: relative;
opacity: 0;
cursor: pointer;
color: var(--content-color);
transition: opacity 0.15s;
&:hover {
color: var(--caption-color);
}
&::before {
position: absolute;
content: '';
inset: -0.5rem;
}
}
.circles-mark {
position: relative;
opacity: 0;

View File

@ -1,106 +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 presentation from '@hcengineering/presentation'
import { Icon, IconEdit, IconClose, tooltip, Button } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let isEditable = false
export let isDeletable = false
export let isEditing = false
export let isSaving = false
export let canSave = false
const dispatch = createEventDispatcher()
$: areButtonsVisible = isEditable || isDeletable || isEditing
</script>
<div
class="root flex flex-between items-center w-full p-2"
on:dblclick|preventDefault={isEditable && !isEditing ? () => dispatch('edit') : undefined}
>
<div class="content w-full">
<slot />
</div>
{#if areButtonsVisible}
<div class="ml-auto pl-2 buttons-group small-gap flex-no-shrink">
{#if isEditing}
<Button label={presentation.string.Cancel} kind="secondary" on:click={() => dispatch('cancel')} />
<Button
label={presentation.string.Save}
kind="primary"
loading={isSaving}
disabled={!canSave}
on:click={() => dispatch('save')}
/>
{:else}
{#if isEditable}
<button
class="btn"
use:tooltip={{ label: presentation.string.Edit }}
on:click|preventDefault={() => dispatch('edit')}
>
<Icon icon={IconEdit} size="small" />
</button>
{/if}
{#if isDeletable}
<button
class="btn"
use:tooltip={{ label: presentation.string.Remove }}
on:click|preventDefault={() => dispatch('delete')}
>
<Icon icon={IconClose} size="small" />
</button>
{/if}
{/if}
</div>
{/if}
</div>
<style lang="scss">
.root {
overflow: hidden;
&:hover {
.btn {
opacity: 1;
}
}
}
.content {
overflow: hidden;
}
.btn {
position: relative;
opacity: 0;
cursor: pointer;
color: var(--content-color);
transition: opacity 0.15s;
&:hover {
color: var(--caption-color);
}
&::before {
position: absolute;
content: '';
inset: -0.5rem;
}
}
</style>

View File

@ -56,7 +56,6 @@ import ValueSelector from './components/ValueSelector.svelte'
import HTMLEditor from './components/HTMLEditor.svelte'
import SortableList from './components/list/SortableList.svelte'
import SortableListItem from './components/list/SortableListItem.svelte'
import SortableListItemPresenter from './components/list/SortableListItemPresenter.svelte'
import {
afterResult,
beforeResult,
@ -117,8 +116,7 @@ export {
NumberPresenter,
TimestampPresenter,
SortableList,
SortableListItem,
SortableListItemPresenter
SortableListItem
}
export default async (): Promise<Resources> => ({