mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 19:11:33 +03:00
Fix AccordionEditor and UI (#2436)
This commit is contained in:
parent
2a4661748d
commit
12c244ddf3
@ -150,11 +150,13 @@
|
||||
{getName(selected)}
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex-row-center">
|
||||
<div class="flex-presenter">
|
||||
{#if icon}
|
||||
<Icon {icon} size={kind === 'link' ? 'small' : size} />
|
||||
<div class="icon" class:small-gap={size === 'inline' || size === 'small'}>
|
||||
<Icon {icon} size={kind === 'link' ? 'small' : size} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="ml-2">
|
||||
<div class="label no-underline">
|
||||
<Label {label} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -25,9 +25,10 @@
|
||||
export let icon: Asset | AnySvelteComponent | undefined = undefined
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="flex-row-center" on:click>
|
||||
<Avatar avatar={value.avatar} {size} {icon} />
|
||||
<div class="flex-col ml-2 min-w-0">
|
||||
<div class="flex-col min-w-0 {size === 'tiny' || size === 'inline' ? 'ml-1' : 'ml-2'}">
|
||||
{#if subtitle}<div class="content-dark-color text-sm">{subtitle}</div>{/if}
|
||||
<div class="content-accent-color overflow-label text-left">{formatName(value.name)}</div>
|
||||
</div>
|
||||
|
@ -22,7 +22,7 @@
|
||||
export let emphasized: boolean = false
|
||||
export let alwaysEdit: boolean = false
|
||||
export let showButtons: boolean = true
|
||||
export let showAttach: boolean = false
|
||||
export let hideAttachments: boolean = false
|
||||
export let buttonSize: IconSize = 'small'
|
||||
export let hideExtraButtons: boolean = false
|
||||
export let maxHeight: 'max' | 'card' | 'limited' | string = 'max'
|
||||
@ -113,7 +113,7 @@
|
||||
<StyledTextEditor
|
||||
{placeholder}
|
||||
{showButtons}
|
||||
{showAttach}
|
||||
{hideAttachments}
|
||||
{buttonSize}
|
||||
{maxHeight}
|
||||
{focusable}
|
||||
@ -126,6 +126,7 @@
|
||||
}}
|
||||
on:blur={() => {
|
||||
focused = false
|
||||
dispatch('blur', rawValue)
|
||||
if (alwaysEdit) {
|
||||
dispatch('value', rawValue)
|
||||
content = rawValue
|
||||
|
@ -60,7 +60,7 @@
|
||||
export let content: string = ''
|
||||
export let placeholder: IntlString = textEditorPlugin.string.EditorPlaceholder
|
||||
export let showButtons: boolean = true
|
||||
export let showAttach: boolean = false
|
||||
export let hideAttachments: boolean = false
|
||||
export let buttonSize: IconSize = 'large'
|
||||
export let isScrollable: boolean = true
|
||||
export let focusable: boolean = false
|
||||
@ -118,7 +118,7 @@
|
||||
dispatch('attach')
|
||||
},
|
||||
order: 1000,
|
||||
hidden: showAttach
|
||||
hidden: hideAttachments
|
||||
},
|
||||
{
|
||||
label: textEditorPlugin.string.TextStyle,
|
||||
@ -496,7 +496,7 @@
|
||||
{#if $$slots.right}
|
||||
<div class="flex-between">
|
||||
<div class="buttons-group xsmall-gap mt-4">
|
||||
{#each defActions.filter((it) => it.hidden === undefined || it.hidden === true) as a}
|
||||
{#each defActions.filter((it) => it.hidden === undefined || it.hidden === false) as a}
|
||||
<StyleButton icon={a.icon} size={buttonSize} on:click={(evt) => handleAction(a, evt)} />
|
||||
{/each}
|
||||
<slot />
|
||||
@ -507,7 +507,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="buttons-group xsmall-gap mt-4">
|
||||
{#each defActions.filter((it) => it.hidden === undefined || it.hidden === true) as a}
|
||||
{#each defActions.filter((it) => it.hidden === undefined || it.hidden === false) as a}
|
||||
<StyleButton icon={a.icon} size={buttonSize} on:click={(evt) => handleAction(a, evt)} />
|
||||
{/each}
|
||||
<slot />
|
||||
|
@ -241,7 +241,6 @@ input.search {
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
margin-right: .5rem;
|
||||
color: var(--dark-color);
|
||||
|
||||
&.circle {
|
||||
@ -249,6 +248,8 @@ input.search {
|
||||
background-color: var(--avatar-bg-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
&:not(.small-gap) { margin-right: .5rem; }
|
||||
&.small-gap { margin-right: .25rem; }
|
||||
}
|
||||
.label {
|
||||
min-width: 0;
|
||||
@ -275,10 +276,11 @@ input.search {
|
||||
margin-left: .75rem;
|
||||
}
|
||||
&:hover {
|
||||
.icon { color: var(--theme-caption-color); }
|
||||
.icon { color: var(--caption-color); }
|
||||
.label {
|
||||
text-decoration: underline;
|
||||
color: var(--theme-caption-color);
|
||||
color: var(--caption-color);
|
||||
|
||||
&:not(.no-underline) { text-decoration: underline; }
|
||||
}
|
||||
.action { visibility: visible; }
|
||||
}
|
||||
@ -356,17 +358,17 @@ input.search {
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
width: fit-content;
|
||||
color: var(--theme-caption-color);
|
||||
color: var(--caption-color);
|
||||
cursor: pointer;
|
||||
.icon {
|
||||
margin-right: .25rem;
|
||||
color: var(--theme-content-dark-color);
|
||||
color: var(--dark-color);
|
||||
&.small-size {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
}
|
||||
&:hover .icon { color: var(--theme-caption-color); }
|
||||
&:hover .icon { color: var(--caption-color); }
|
||||
}
|
||||
|
||||
/* Margins & Paddings */
|
||||
|
@ -414,6 +414,9 @@
|
||||
max-height: 1.5rem;
|
||||
font-weight: 400;
|
||||
transition: opacity .15s var(--timing-main);
|
||||
|
||||
&::-webkit-scrollbar:vertical { width: 0; }
|
||||
&::-webkit-scrollbar:horizontal { height: 0; }
|
||||
}
|
||||
.rotated-icon {
|
||||
transform-origin: center;
|
||||
@ -426,6 +429,7 @@
|
||||
|
||||
&.opened {
|
||||
.caption .value { opacity: 0; }
|
||||
.expand-collapse .expand-collapse,
|
||||
.expand-collapse {
|
||||
visibility: visible;
|
||||
max-height: max-content;
|
||||
@ -436,13 +440,19 @@
|
||||
margin-bottom: -.5rem;
|
||||
|
||||
.value { opacity: 1; }
|
||||
&.hasAttachments { margin-bottom: 0; }
|
||||
}
|
||||
.expand-collapse {
|
||||
.expand-collapse .expand-collapse,
|
||||
.expand-collapse:not(.hasAttachments) {
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
max-height: 0;
|
||||
}
|
||||
&:hover .caption { margin-bottom: 0rem; }
|
||||
&:hover .caption {
|
||||
margin-bottom: 0rem;
|
||||
|
||||
&.hasAttachments { margin-bottom: .5rem; }
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
|
@ -214,8 +214,8 @@
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
background-color: var(--board-card-bg-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
border-top: 1px solid var(--board-card-bg-color);
|
||||
border-left: 1px solid var(--divider-color);
|
||||
border-bottom-right-radius: .45rem;
|
||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
|
||||
transition: box-shadow 150ms ease 0s, transform 150ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
transform: translateX(0);
|
||||
@ -241,10 +241,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.asideShown .popupPanel-body__main {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 0 0 .5rem .5rem;
|
||||
}
|
||||
// &.asideShown .popupPanel-body__main {
|
||||
// border: 1px solid var(--divider-color);
|
||||
// border-radius: 0 0 .5rem .5rem;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span slot="content" class="overflow-label disabled" class:dark-color={selectedItem === undefined}>
|
||||
<span slot="content" class="overflow-label disabled" class:content-accent-color={selectedItem === undefined}>
|
||||
{#if selectedItem}{selectedItem.label}{:else}<Label label={label ?? ui.string.NotSelected} />{/if}
|
||||
</span>
|
||||
</Button>
|
||||
|
@ -16,11 +16,12 @@
|
||||
export let column: number = 2
|
||||
export let rowGap: number = 2.5
|
||||
export let columnGap: number = 1.5
|
||||
export let topGap: boolean = false
|
||||
|
||||
$: style = `grid-template-columns: repeat(${column}, 1fr); row-gap: ${rowGap}rem; column-gap: ${columnGap}rem;`
|
||||
</script>
|
||||
|
||||
<div class="grid" {style}>
|
||||
<div class="grid" {style} style:margin-top={topGap ? `${rowGap}rem` : 0}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
|
@ -21,18 +21,20 @@
|
||||
import AttachmentStyledBox from './AttachmentStyledBox.svelte'
|
||||
|
||||
export let items: AccordionItem[]
|
||||
export let objectId: Ref<Doc>
|
||||
export let space: Ref<Space>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let objectId: Ref<Doc> | undefined = undefined
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let _class: Ref<Class<Doc>> | undefined = undefined
|
||||
export let withoutAttach: boolean = false
|
||||
|
||||
export function createAttachments (): void {
|
||||
attachments.forEach((at) => at.createAttachments())
|
||||
attachments[attachments.length - 1].createAttachments()
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const attachments: AttachmentStyledBox[] = []
|
||||
const edits: TextEditor[] = []
|
||||
let hasAttachments: boolean = false
|
||||
|
||||
const flip = (index: number, ev?: MouseEvent): void => {
|
||||
ev?.stopPropagation()
|
||||
@ -41,7 +43,7 @@
|
||||
case 'opened':
|
||||
attachments[index].setEditable(false)
|
||||
items[index].state = 'closed'
|
||||
setTimeout(() => edits[index].focus('end'), 0)
|
||||
setTimeout(() => edits[index].focus(), 0)
|
||||
break
|
||||
case 'closed':
|
||||
items[index].state = 'opened'
|
||||
@ -59,6 +61,7 @@
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="caption"
|
||||
class:hasAttachments={hasAttachments && i === items.length - 1}
|
||||
use:tooltip={{ label: item.tooltip }}
|
||||
tabindex="-1"
|
||||
on:click={() => {
|
||||
@ -80,6 +83,7 @@
|
||||
dispatch('update', { item, value: ev.detail })
|
||||
flip(i)
|
||||
}}
|
||||
on:blur={() => dispatch('blur', item)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@ -91,17 +95,26 @@
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="expand-collapse">
|
||||
<div class="expand-collapse" class:hasAttachments={hasAttachments && i === items.length - 1}>
|
||||
<AttachmentStyledBox
|
||||
bind:this={attachments[i]}
|
||||
alwaysEdit
|
||||
showButtons
|
||||
fakeAttach={withoutAttach ? 'hidden' : i < items.length - 1 ? 'fake' : 'normal'}
|
||||
bind:content={item.content}
|
||||
placeholder={textEditorPlugin.string.EditorPlaceholder}
|
||||
{objectId}
|
||||
{_class}
|
||||
{space}
|
||||
on:changeContent={(ev) => dispatch('update', { item, value: ev.detail })}
|
||||
on:attach={(ev) => {
|
||||
if (ev && ev.detail.action === 'drop') attachments[attachments.length - 1].fileDrop(ev.detail.event)
|
||||
else if (ev.detail.action === 'add') attachments[attachments.length - 1].attach()
|
||||
else if (ev.detail.action === 'saved') {
|
||||
if (ev.detail.value !== hasAttachments) hasAttachments = ev.detail.value
|
||||
}
|
||||
}}
|
||||
on:blur={() => dispatch('blur', item)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,14 +19,14 @@
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||
import { IconSize } from '@hcengineering/ui'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import attachment from '../plugin'
|
||||
import { deleteFile, uploadFile } from '../utils'
|
||||
import AttachmentPresenter from './AttachmentPresenter.svelte'
|
||||
|
||||
export let objectId: Ref<Doc>
|
||||
export let space: Ref<Space>
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let objectId: Ref<Doc> | undefined = undefined
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let _class: Ref<Class<Doc>> | undefined = undefined
|
||||
export let content: string = ''
|
||||
export let placeholder: IntlString | undefined = undefined
|
||||
export let alwaysEdit = false
|
||||
@ -35,8 +35,11 @@
|
||||
export let buttonSize: IconSize = 'small'
|
||||
export let maxHeight: 'max' | 'card' | 'limited' | string = 'max'
|
||||
export let focusable: boolean = false
|
||||
export let fakeAttach: 'fake' | 'hidden' | 'normal' = 'normal'
|
||||
export let refContainer: HTMLElement | undefined = undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export function focus (): void {
|
||||
refInput.focus()
|
||||
}
|
||||
@ -81,6 +84,7 @@
|
||||
)
|
||||
|
||||
async function createAttachment (file: File) {
|
||||
if (space === undefined || objectId === undefined || _class === undefined) return
|
||||
try {
|
||||
const uuid = await uploadFile(file, { space, attachedTo: objectId })
|
||||
const _id: Ref<Attachment> = generateId()
|
||||
@ -107,6 +111,7 @@
|
||||
}
|
||||
|
||||
async function saveAttachment (doc: Attachment): Promise<void> {
|
||||
if (space === undefined || objectId === undefined || _class === undefined) return
|
||||
await client.addCollection(attachment.class.Attachment, space, objectId, _class, 'attachments', doc, doc._id)
|
||||
}
|
||||
|
||||
@ -120,12 +125,13 @@
|
||||
inputFile.value = ''
|
||||
}
|
||||
|
||||
function fileDrop (e: DragEvent) {
|
||||
export function fileDrop (e: DragEvent) {
|
||||
const list = e.dataTransfer?.files
|
||||
if (list === undefined || list.length === 0) return
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const file = list.item(index)
|
||||
if (file !== null) createAttachment(file)
|
||||
if (list !== undefined && list.length !== 0) {
|
||||
for (let index = 0; index < list.length; index++) {
|
||||
const file = list.item(index)
|
||||
if (file !== null) createAttachment(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +182,10 @@
|
||||
return Promise.all(promises).then()
|
||||
}
|
||||
|
||||
$: if (attachments.size || newAttachments.size || removedAttachments.size) {
|
||||
dispatch('attach', { action: 'saved', value: attachments.size })
|
||||
}
|
||||
|
||||
function isAllowedPaste (evt: ClipboardEvent) {
|
||||
let t: HTMLElement | null = evt.target as HTMLElement
|
||||
|
||||
@ -193,7 +203,7 @@
|
||||
return false
|
||||
}
|
||||
|
||||
function pasteAction (evt: ClipboardEvent): void {
|
||||
export function pasteAction (evt: ClipboardEvent): void {
|
||||
if (!isAllowedPaste(evt)) {
|
||||
return
|
||||
}
|
||||
@ -211,7 +221,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:paste={pasteAction} />
|
||||
<svelte:window on:paste={(ev) => (fakeAttach === 'normal' ? pasteAction(ev) : undefined)} />
|
||||
|
||||
<input
|
||||
bind:this={inputFile}
|
||||
@ -227,24 +237,33 @@
|
||||
class="flex-col clear-mins"
|
||||
on:dragover|preventDefault={() => {}}
|
||||
on:dragleave={() => {}}
|
||||
on:drop|preventDefault|stopPropagation={fileDrop}
|
||||
on:drop|preventDefault|stopPropagation={(ev) => {
|
||||
if (fakeAttach === 'fake') dispatch('attach', { action: 'drop', event: ev })
|
||||
else if (fakeAttach === 'normal') fileDrop(ev)
|
||||
}}
|
||||
>
|
||||
<StyledTextBox
|
||||
bind:this={refInput}
|
||||
bind:content
|
||||
{placeholder}
|
||||
{alwaysEdit}
|
||||
{showButtons}
|
||||
showAttach
|
||||
{buttonSize}
|
||||
{maxHeight}
|
||||
{focusable}
|
||||
{emphasized}
|
||||
on:changeSize
|
||||
on:changeContent
|
||||
on:attach={() => attach()}
|
||||
/>
|
||||
{#if attachments.size}
|
||||
<div class="expand-collapse">
|
||||
<StyledTextBox
|
||||
bind:this={refInput}
|
||||
bind:content
|
||||
{placeholder}
|
||||
{alwaysEdit}
|
||||
{showButtons}
|
||||
hideAttachments={fakeAttach === 'hidden'}
|
||||
{buttonSize}
|
||||
{maxHeight}
|
||||
{focusable}
|
||||
{emphasized}
|
||||
on:changeSize
|
||||
on:changeContent
|
||||
on:blur
|
||||
on:attach={() => {
|
||||
if (fakeAttach === 'fake') dispatch('attach', { action: 'add' })
|
||||
else if (fakeAttach === 'normal') attach()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if attachments.size && fakeAttach === 'normal'}
|
||||
<div class="flex-row-center list scroll-divider-color">
|
||||
{#each Array.from(attachments.values()) as attachment}
|
||||
<div class="item flex">
|
||||
|
@ -87,10 +87,12 @@
|
||||
}
|
||||
)
|
||||
}
|
||||
$: smallgap = size === 'inline' || size === 'small'
|
||||
</script>
|
||||
|
||||
{#if value && statuses}
|
||||
{#if kind === 'list' || kind === 'list-header'}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="flex-row-center flex-no-shrink" class:cursor-pointer={isEditable} on:click={handleStatusEditorOpened}>
|
||||
<div class="flex-center flex-no-shrink square-4">
|
||||
{#if selectedStatus}<IssueStatusIcon value={selectedStatus} size={kind === 'list' ? 'inline' : 'medium'} />{/if}
|
||||
@ -98,8 +100,8 @@
|
||||
{#if selectedStatusLabel}
|
||||
<span
|
||||
class="{kind === 'list'
|
||||
? 'ml-2 text-md'
|
||||
: 'ml-3 text-base'} overflow-label disabled fs-bold content-accent-color"
|
||||
? 'ml-1 text-md'
|
||||
: 'ml-2 text-base'} overflow-label disabled fs-bold content-accent-color"
|
||||
>
|
||||
{selectedStatusLabel}
|
||||
</span>
|
||||
@ -115,12 +117,18 @@
|
||||
{width}
|
||||
on:click={handleStatusEditorOpened}
|
||||
>
|
||||
<span slot="content" class="inline-flex pointer-events-none">
|
||||
<span slot="content" class="flex-row-center pointer-events-none">
|
||||
{#if selectedStatus}
|
||||
<IssueStatusIcon value={selectedStatus} size="inline" />
|
||||
{/if}
|
||||
{#if selectedStatusLabel}
|
||||
<span class="overflow-label disabled" class:ml-2={selectedStatus}>{selectedStatusLabel}</span>
|
||||
<span
|
||||
class="overflow-label disabled"
|
||||
class:ml-1={selectedStatus && smallgap}
|
||||
class:ml-2={selectedStatus && !smallgap}
|
||||
>
|
||||
{selectedStatusLabel}
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
</Button>
|
||||
|
Loading…
Reference in New Issue
Block a user