mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-26 04:23:58 +03:00
Add horizontal view to the sortable list (#2412)
Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@xored.com>
This commit is contained in:
parent
31c7586ddf
commit
3d53ebc8b9
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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>
|
@ -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> => ({
|
||||
|
Loading…
Reference in New Issue
Block a user