Chunter: FileBrowser - add grid view (#1571)

Signed-off-by: Ruslan Izhitsky <ruslan.izhitskiy@xored.com>
This commit is contained in:
Ruslan Izhitsky 2022-04-28 13:45:07 +07:00 committed by GitHub
parent ed3d59044d
commit e9a0225dd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 558 additions and 143 deletions

View File

@ -17,6 +17,8 @@
"Name": "Name",
"FileBrowser": "File browser",
"FileBrowserFileCounter": "{results, plural, =1 {# result} other {# results}}",
"FileBrowserListView": "View as list",
"FileBrowserGridView": "View as grid",
"FileBrowserFilterFrom": "From",
"FileBrowserFilterIn": "In",
"FileBrowserFilterDate": "Date",

View File

@ -17,6 +17,8 @@
"Name": "Название",
"FileBrowser": "Браузер файлов",
"FileBrowserFileCounter": "{results, plural, =1 {# результат} =2 {# результата} =3 {# результата} =4 {# результата} other {# результатов}}",
"FileBrowserListView": "Показать в виде списка",
"FileBrowserGridView": "Показать в виде таблицы",
"FileBrowserFilterFrom": "От",
"FileBrowserFilterIn": "В",
"FileBrowserFilterDate": "Дата",

View File

@ -0,0 +1,200 @@
<!--
// 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 type { Attachment } from '@anticrm/attachment'
import { showPopup, closeTooltip } from '@anticrm/ui'
import { PDFViewer, getFileUrl } from '@anticrm/presentation'
import filesize from 'filesize'
export let value: Attachment
const maxLength: number = 18
const trimFilename = (fname: string): string =>
fname.length > maxLength ? fname.substr(0, (maxLength - 1) / 2) + '...' + fname.substr(-(maxLength - 1) / 2) : fname
function extensionIconLabel (name: string): string {
const parts = name.split('.')
const ext = parts[parts.length - 1]
return ext.substring(0, 4).toUpperCase()
}
function isPDF (contentType: string) {
return contentType.includes('application/pdf')
}
function isImage (contentType: string) {
return contentType.startsWith('image/')
}
function isEmbedded (contentType: string) {
return isPDF(contentType) || isImage(contentType)
}
function openAttachment () {
closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right')
}
</script>
<div class="gridCellOverlay">
<div class="gridCell">
{#if isImage(value.type)}
<div class="cellImagePreview" on:click={openAttachment}>
<img class={'img-fit'} src={getFileUrl(value.file)} alt={value.name} />
</div>
{:else}
<div class="cellMiscPreview">
{#if isPDF(value.type)}
<div class="flex-center extensionIcon" on:click={openAttachment}>
{extensionIconLabel(value.name)}
</div>
{:else}
<a class="no-line" href={getFileUrl(value.file)} download={value.name}>
<div class="flex-center extensionIcon">{extensionIconLabel(value.name)}</div>
</a>
{/if}
</div>
{/if}
<div class="cellInfo">
{#if isEmbedded(value.type)}
<div class="flex-center extensionIcon" on:click={openAttachment}>
{extensionIconLabel(value.name)}
</div>
{:else}
<a class="no-line" href={getFileUrl(value.file)} download={value.name}>
<div class="flex-center extensionIcon">{extensionIconLabel(value.name)}</div>
</a>
{/if}
<div class="eCellInfoData">
{#if isEmbedded(value.type)}
<div class="eCellInfoFilename" on:click={openAttachment}>
{trimFilename(value.name)}
</div>
{:else}
<div class="eCellInfoFilename">
<a href={getFileUrl(value.file)} download={value.name}>{trimFilename(value.name)}</a>
</div>
{/if}
<div class="eCellInfoFilesize">{filesize(value.size)}</div>
</div>
<div class="eCellInfoMenu"><slot name="rowMenu" /></div>
</div>
</div>
</div>
<style lang="scss">
.gridCellOverlay {
position: relative;
padding: 4px;
}
.gridCell {
position: relative;
height: 100%;
display: flex;
flex-direction: column;
border-radius: 12px;
justify-content: space-between;
overflow: hidden;
box-shadow: 0 0 0 1px var(--theme-bg-focused-border);
}
.cellImagePreview {
display: flex;
justify-content: center;
height: 160px;
overflow: hidden;
margin: 0 1.5rem;
border-radius: 0.5rem;
background-color: var(--theme-menu-color);
cursor: pointer;
}
.cellMiscPreview {
display: flex;
justify-content: center;
align-items: center;
height: 160px;
}
.img-fit {
object-fit: cover;
height: 100%;
width: 100%;
}
.cellInfo {
display: flex;
flex-direction: row;
padding: 12px;
min-height: 36px;
align-items: center;
.eCellInfoData {
display: flex;
flex-direction: column;
margin-left: 1rem;
}
.eCellInfoMenu {
margin-left: auto;
}
.eCellInfoFilename {
font-weight: 500;
color: var(--theme-content-accent-color);
white-space: nowrap;
cursor: pointer;
}
.eCellInfoFilesize {
font-size: 0.75rem;
color: var(--theme-content-dark-color);
}
}
.extensionIcon {
flex-shrink: 0;
width: 2rem;
height: 2rem;
font-weight: 500;
font-size: 0.625rem;
color: var(--primary-button-color);
background-color: var(--primary-button-enabled);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
cursor: pointer;
}
.eCellInfoFilename:hover,
.extensionIcon:hover + .eCellInfoData > .eCellInfoFilename, // embedded on extension hover
.no-line:hover + .eCellInfoData > .eCellInfoFilename a, // not embedded on extension hover
.cellImagePreview:hover + .cellInfo .eCellInfoFilename, // image on preview hover
.cellMiscPreview:hover + .cellInfo .eCellInfoFilename, // PDF on preview hover
.cellMiscPreview:hover + .cellInfo .eCellInfoFilename a // not embedded on preview hover
{
text-decoration: underline;
color: var(--theme-caption-color);
}
.eCellInfoFilename:active,
.extensionIcon:active + .eCellInfoData > .eCellInfoFilename, // embedded on extension hover
.no-line:active + .eCellInfoData > .eCellInfoFilename a, // not embedded on extension hover
.cellImagePreview:active + .cellInfo .eCellInfoFilename, // image on preview hover
.cellMiscPreview:active + .cellInfo .eCellInfoFilename, // PDF on preview hover
.cellMiscPreview:active + .cellInfo .eCellInfoFilename a // not embedded on preview hover
{
text-decoration: underline;
color: var(--theme-content-accent-color);
}
</style>

View File

@ -13,7 +13,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Attachment } from '@anticrm/attachment'
import { showPopup, closeTooltip } from '@anticrm/ui'
@ -23,9 +22,8 @@
export let value: Attachment
const maxLenght: number = 16
const trimFilename = (fname: string): string => (fname.length > maxLenght)
? fname.substr(0, (maxLenght - 1) / 2) + '...' + fname.substr(-(maxLenght - 1) / 2)
: fname
const trimFilename = (fname: string): string =>
fname.length > maxLenght ? fname.substr(0, (maxLenght - 1) / 2) + '...' + fname.substr(-(maxLenght - 1) / 2) : fname
function iconLabel (name: string): string {
const parts = name.split('.')
@ -40,18 +38,33 @@
<div class="flex-row-center">
{#if openEmbedded(value.type)}
<div class="flex-center icon" on:click={() => {
closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right')
}}>{iconLabel(value.name)}</div>
<div
class="flex-center icon"
on:click={() => {
closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right')
}}
>
{iconLabel(value.name)}
</div>
{:else}
<a class="no-line" href={getFileUrl(value.file)} download={value.name}><div class="flex-center icon">{iconLabel(value.name)}</div></a>
<a class="no-line" href={getFileUrl(value.file)} download={value.name}
><div class="flex-center icon">{iconLabel(value.name)}</div></a
>
{/if}
<div class="flex-col info">
{#if openEmbedded(value.type)}
<div class="name" on:click={() => { closeTooltip(); showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right') }}>{trimFilename(value.name)}</div>
<div
class="name"
on:click={() => {
closeTooltip()
showPopup(PDFViewer, { file: value.file, name: value.name, contentType: value.type }, 'right')
}}
>
{trimFilename(value.name)}
</div>
{:else}
<div class="name"><a href={getFileUrl(value.file)} download={value.name}>{trimFilename(value.name)}</a></div>
<div class="name"><a href={getFileUrl(value.file)} download={value.name}>{trimFilename(value.name)}</a></div>
{/if}
<div class="type">{filesize(value.size)}</div>
</div>
@ -64,11 +77,11 @@
width: 2rem;
height: 2rem;
font-weight: 500;
font-size: .625rem;
font-size: 0.625rem;
color: var(--primary-button-color);
background-color: var(--primary-button-enabled);
border: 1px solid rgba(0, 0, 0, .1);
border-radius: .5rem;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.5rem;
cursor: pointer;
}
@ -80,15 +93,19 @@
}
.type {
font-size: .75rem;
font-size: 0.75rem;
color: var(--theme-content-dark-color);
}
.name:hover, .icon:hover + .info > .name, .no-line:hover + .info > .name a {
.name:hover,
.icon:hover + .info > .name,
.no-line:hover + .info > .name a {
text-decoration: underline;
color: var(--theme-caption-color);
}
.name:active, .icon:active + .info > .name, .no-line:active + .info > .name a {
.name:active,
.icon:active + .info > .name,
.no-line:active + .info > .name a {
text-decoration: underline;
color: var(--theme-content-accent-color);
}

View File

@ -1,4 +1,3 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
@ -44,15 +43,20 @@
const query = createQuery()
let attachments: Map<Ref<Attachment>, Attachment> = new Map<Ref<Attachment>, Attachment>()
let originalAttachments: Set<Ref<Attachment>> = new Set<Ref<Attachment>>()
let newAttachments: Set<Ref<Attachment>> = new Set<Ref<Attachment>>()
let removedAttachments: Set<Attachment> = new Set<Attachment>()
const newAttachments: Set<Ref<Attachment>> = new Set<Ref<Attachment>>()
const removedAttachments: Set<Attachment> = new Set<Attachment>()
$: objectId && query.query(attachment.class.Attachment, {
attachedTo: objectId
}, (res) => {
originalAttachments = new Set(res.map((p) => p._id))
attachments = new Map(res.map((p) => [p._id, p]))
})
$: objectId &&
query.query(
attachment.class.Attachment,
{
attachedTo: objectId
},
(res) => {
originalAttachments = new Set(res.map((p) => p._id))
attachments = new Map(res.map((p) => [p._id, p]))
}
)
async function createAttachment (file: File) {
try {
@ -82,7 +86,15 @@
}
async function saveAttachment (doc: Attachment) {
const res = await client.addCollection(attachment.class.Attachment, space, objectId, _class, 'attachments', doc, doc._id)
const res = await client.addCollection(
attachment.class.Attachment,
space,
objectId,
_class,
'attachments',
doc,
doc._id
)
}
function fileSelected () {
@ -112,7 +124,14 @@
async function deleteAttachment (attachment: Attachment): Promise<void> {
if (originalAttachments.has(attachment._id)) {
await client.removeCollection(attachment._class, attachment.space, attachment._id, attachment.attachedTo, attachment.attachedToClass, 'attachments')
await client.removeCollection(
attachment._class,
attachment.space,
attachment._id,
attachment.attachedTo,
attachment.attachedToClass,
'attachments'
)
} else {
await deleteFile(attachment.file)
}
@ -144,7 +163,6 @@
await Promise.all(promises)
dispatch('message', { message: event.detail, attachments: attachments.size })
}
</script>
<input
@ -155,25 +173,41 @@
id="file"
style="display: none"
on:change={fileSelected}
/>
<div class="container"
/>
<div
class="container"
on:dragover|preventDefault={() => {}}
on:dragleave={() => {}}
on:drop|preventDefault|stopPropagation={fileDrop}
>
>
{#if attachments.size}
<div class='flex-row-center list'>
<div class="flex-row-center list">
{#each Array.from(attachments.values()) as attachment}
<div class='item flex'>
<div class="item flex">
<AttachmentPresenter value={attachment} />
<div class='remove'>
<ActionIcon icon={IconClose} action={() => { removeAttachment(attachment) }} size='small' />
<div class="remove">
<ActionIcon
icon={IconClose}
action={() => {
removeAttachment(attachment)
}}
size="small"
/>
</div>
</div>
{/each}
</div>
{/if}
<ReferenceInput bind:this={refInput} {content} {showSend} on:message={onMessage} withoutTopBorder={attachments.size > 0} on:attach={() => { inputFile.click() }} />
<ReferenceInput
bind:this={refInput}
{content}
{showSend}
on:message={onMessage}
withoutTopBorder={attachments.size > 0}
on:attach={() => {
inputFile.click()
}}
/>
</div>
<style lang="scss">
@ -183,7 +217,7 @@
overflow-x: auto;
background-color: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: .75rem;
border-radius: 0.75rem;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;

View File

@ -0,0 +1,98 @@
<!--
// 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 { Attachment } from '@anticrm/attachment'
import { Doc } from '@anticrm/core'
import { getFileUrl } from '@anticrm/presentation'
import { Icon, IconMoreV, showPopup } from '@anticrm/ui'
import { Menu } from '@anticrm/view-resources'
import FileDownload from './icons/FileDownload.svelte'
import { AttachmentGalleryPresenter } from '..'
export let attachments: Attachment[]
let selectedFileNumber: number | undefined
const showFileMenu = async (ev: MouseEvent, object: Doc, fileNumber: number): Promise<void> => {
selectedFileNumber = fileNumber
showPopup(Menu, { object }, ev.target as HTMLElement, () => {
selectedFileNumber = undefined
})
}
</script>
<div class="galleryGrid">
{#each attachments as attachment, i}
<div class="attachmentCell" class:fixed={i === selectedFileNumber}>
<AttachmentGalleryPresenter value={attachment}>
<svelte:fragment slot="rowMenu">
<div class="eAttachmentCellActions" class:fixed={i === selectedFileNumber}>
<a href={getFileUrl(attachment.file)} download={attachment.name}>
<Icon icon={FileDownload} size={'small'} />
</a>
<div class="eAttachmentCellMenu" on:click={(event) => showFileMenu(event, attachment, i)}>
<IconMoreV size={'small'} />
</div>
</div>
</svelte:fragment>
</AttachmentGalleryPresenter>
</div>
{/each}
</div>
<style lang="scss">
.galleryGrid {
display: grid;
margin: 0 1.5rem;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.attachmentCell {
.eAttachmentCellActions {
display: flex;
visibility: hidden;
border: 1px solid var(--theme-bg-focused-border);
padding: 0.2rem;
border-radius: 0.375rem;
}
.eAttachmentCellMenu {
visibility: hidden;
margin-left: 0.2rem;
opacity: 0.6;
cursor: pointer;
&:hover {
opacity: 1;
}
}
&:hover {
.eAttachmentCellActions {
visibility: visible;
}
.eAttachmentCellMenu {
visibility: visible;
}
}
&.fixed {
.eAttachmentCellActions {
visibility: visible;
}
.eAttachmentCellMenu {
visibility: visible;
}
}
}
</style>

View File

@ -0,0 +1,96 @@
<!--
// 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 { Attachment } from '@anticrm/attachment'
import { Doc } from '@anticrm/core'
import { getFileUrl } from '@anticrm/presentation'
import { Icon, IconMoreV, showPopup } from '@anticrm/ui'
import { Menu } from '@anticrm/view-resources'
import FileDownload from './icons/FileDownload.svelte'
import { AttachmentPresenter } from '..'
export let attachments: Attachment[]
let selectedFileNumber: number | undefined
const showFileMenu = async (ev: MouseEvent, object: Doc, fileNumber: number): Promise<void> => {
selectedFileNumber = fileNumber
showPopup(Menu, { object }, ev.target as HTMLElement, () => {
selectedFileNumber = undefined
})
}
</script>
<div class="flex-col">
{#each attachments as attachment, i}
<div class="flex-between attachmentRow" class:fixed={i === selectedFileNumber}>
<div class="item flex">
<AttachmentPresenter value={attachment} />
</div>
<div class="eAttachmentRowActions" class:fixed={i === selectedFileNumber}>
<a href={getFileUrl(attachment.file)} download={attachment.name}>
<Icon icon={FileDownload} size={'small'} />
</a>
<div class="eAttachmentRowMenu" on:click={(event) => showFileMenu(event, attachment, i)}>
<IconMoreV size={'small'} />
</div>
</div>
</div>
{/each}
</div>
<style lang="scss">
.attachmentRow {
display: flex;
align-items: center;
margin: 0 1.5rem;
padding: 0.25rem 0;
.eAttachmentRowActions {
display: flex;
visibility: hidden;
border: 1px solid var(--theme-bg-focused-border);
padding: 0.2rem;
border-radius: 0.375rem;
}
.eAttachmentRowMenu {
visibility: hidden;
margin-left: 0.2rem;
opacity: 0.6;
cursor: pointer;
&:hover {
opacity: 1;
}
}
&:hover {
.eAttachmentRowActions {
visibility: visible;
}
.eAttachmentRowMenu {
visibility: visible;
}
}
&.fixed {
.eAttachmentRowActions {
visibility: visible;
}
.eAttachmentRowMenu {
visibility: visible;
}
}
}
</style>

View File

@ -16,31 +16,24 @@
import { Attachment } from '@anticrm/attachment'
import contact, { Employee } from '@anticrm/contact'
import { EmployeeAccount } from '@anticrm/contact'
import core, { Class, Doc, getCurrentAccount, Ref, Space } from '@anticrm/core'
import { getClient, getFileUrl } from '@anticrm/presentation'
import core, { Class, getCurrentAccount, Ref, Space } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import ui, {
getCurrentLocation,
location,
IconMoreV,
IconSearch,
Label,
showPopup,
navigate,
EditWithIcon,
Spinner,
Tooltip,
Icon
} from '@anticrm/ui'
import { Menu } from '@anticrm/view-resources'
import { onDestroy } from 'svelte'
import {
AttachmentPresenter,
FileBrowserSortMode,
dateFileBrowserFilters,
fileTypeFileBrowserFilters,
sortModeToOptionObject
} from '..'
import { FileBrowserSortMode, dateFileBrowserFilters, fileTypeFileBrowserFilters, sortModeToOptionObject } from '..'
import attachment from '../plugin'
import FileDownload from './icons/FileDownload.svelte'
import AttachmentsGalleryView from './AttachmentsGalleryView.svelte'
import AttachmentsListView from './AttachmentsListView.svelte'
import FileBrowserFilters from './FileBrowserFilters.svelte'
import FileBrowserSortMenu from './FileBrowserSortMenu.svelte'
@ -55,22 +48,15 @@
let isLoading = false
let attachments: Attachment[] = []
let selectedFileNumber: number | undefined
let selectedSort: FileBrowserSortMode = FileBrowserSortMode.NewestFile
let selectedDateId = 'dateAny'
let selectedFileTypeId = 'typeAny'
const showFileMenu = async (ev: MouseEvent, object: Doc, fileNumber: number): Promise<void> => {
selectedFileNumber = fileNumber
showPopup(Menu, { object }, ev.target as HTMLElement, () => {
selectedFileNumber = undefined
})
}
let isListDisplayMode = true
$: fetch(searchQuery, selectedSort, selectedFileTypeId, selectedDateId, selectedParticipants, selectedSpaces)
async function fetch(
async function fetch (
searchQuery_: string,
selectedSort_: FileBrowserSortMode,
selectedFileTypeId_: string,
@ -131,14 +117,40 @@
</div>
<EditWithIcon icon={IconSearch} bind:value={searchQuery} placeholder={ui.string.SearchDots} />
</div>
<FileBrowserFilters
{requestedSpaceClasses}
{spaceId}
bind:selectedParticipants
bind:selectedSpaces
bind:selectedDateId
bind:selectedFileTypeId
/>
<div class="ac-header full">
<FileBrowserFilters
{requestedSpaceClasses}
{spaceId}
bind:selectedParticipants
bind:selectedSpaces
bind:selectedDateId
bind:selectedFileTypeId
/>
<div class="flex">
<Tooltip label={attachment.string.FileBrowserListView} direction={'bottom'}>
<button
class="ac-header__icon-button"
class:selected={isListDisplayMode}
on:click={() => {
isListDisplayMode = true
}}
>
<Icon icon={contact.icon.Person} size={'small'} />
</button>
</Tooltip>
<Tooltip label={attachment.string.FileBrowserGridView} direction={'bottom'}>
<button
class="ac-header__icon-button"
class:selected={!isListDisplayMode}
on:click={() => {
isListDisplayMode = false
}}
>
<Icon icon={contact.icon.Edit} size={'small'} />
</button>
</Tooltip>
</div>
</div>
<div class="group">
<div class="groupHeader">
<div class="eGroupHeaderCount">
@ -151,23 +163,11 @@
<Spinner />
</div>
{:else if attachments?.length}
<div class="flex-col">
{#each attachments as attachment, i}
<div class="flex-between attachmentRow" class:fixed={i === selectedFileNumber}>
<div class="item flex">
<AttachmentPresenter value={attachment} />
</div>
<div class="eAttachmentRowActions" class:fixed={i === selectedFileNumber}>
<a href={getFileUrl(attachment.file)} download={attachment.name}>
<Icon icon={FileDownload} size={'small'} />
</a>
<div id="context-menu" class="eAttachmentRowMenu" on:click={(event) => showFileMenu(event, attachment, i)}>
<IconMoreV size={'small'} />
</div>
</div>
</div>
{/each}
</div>
{#if isListDisplayMode}
<AttachmentsListView {attachments} />
{:else}
<AttachmentsGalleryView {attachments} />
{/if}
{:else}
<div class="flex-between attachmentRow">
<Label label={attachment.string.NoFiles} />
@ -191,48 +191,4 @@
color: var(--theme-caption-color);
}
}
.attachmentRow {
display: flex;
align-items: center;
padding-right: 1rem;
margin: 0 1.5rem;
padding: 0.25rem 0;
.eAttachmentRowActions {
display: flex;
visibility: hidden;
border: 1px solid var(--theme-bg-focused-border);
padding: 0.2rem;
border-radius: 0.375rem;
}
.eAttachmentRowMenu {
margin-left: 0.2rem;
visibility: hidden;
opacity: 0.6;
cursor: pointer;
&:hover {
opacity: 1;
}
}
&:hover {
.eAttachmentRowActions {
visibility: visible;
}
.eAttachmentRowMenu {
visibility: visible;
}
}
&.fixed {
.eAttachmentRowActions {
visibility: visible;
}
.eAttachmentRowMenu {
visibility: visible;
}
}
}
</style>

View File

@ -17,6 +17,7 @@ import AddAttachment from './components/AddAttachment.svelte'
import AttachmentDroppable from './components/AttachmentDroppable.svelte'
import AttachmentsPresenter from './components/AttachmentsPresenter.svelte'
import AttachmentPresenter from './components/AttachmentPresenter.svelte'
import AttachmentGalleryPresenter from './components/AttachmentGalleryPresenter.svelte'
import AttachmentDocList from './components/AttachmentDocList.svelte'
import AttachmentList from './components/AttachmentList.svelte'
import AttachmentRefInput from './components/AttachmentRefInput.svelte'
@ -27,7 +28,7 @@ import Photos from './components/Photos.svelte'
import FileDownload from './components/icons/FileDownload.svelte'
import { uploadFile, deleteFile } from './utils'
import attachment, { Attachment } from '@anticrm/attachment'
import { SortingOrder, SortingQuery } from '@anticrm/core'
import { ObjQueryType, SortingOrder, SortingQuery } from '@anticrm/core'
import { IntlString, Resources } from '@anticrm/platform'
import preference from '@anticrm/preference'
import { getClient } from '@anticrm/presentation'
@ -38,6 +39,7 @@ export {
Attachments,
AttachmentsPresenter,
AttachmentPresenter,
AttachmentGalleryPresenter,
AttachmentRefInput,
AttachmentList,
AttachmentDocList,
@ -72,18 +74,27 @@ export const sortModeToOptionObject = (sortMode: FileBrowserSortMode): SortingQu
const msInDay = 24 * 60 * 60 * 1000
const getBeginningOfDate = (customDate?: Date) => {
if (!customDate) {
if (customDate == null) {
customDate = new Date()
}
customDate.setUTCHours(0, 0, 0, 0)
return customDate.getTime()
}
export const dateFileBrowserFilters: {
interface Filter {
id: string
label: IntlString<{}>
getDate: () => any
}[] = [
label: IntlString
}
interface DateFilter extends Filter {
getDate: () => ObjQueryType<number> | undefined
}
interface TypeFilter extends Filter {
getType: () => ObjQueryType<string> | undefined
}
export const dateFileBrowserFilters: DateFilter[] = [
{
id: 'dateAny',
label: attachment.string.FileBrowserDateFilterAny,
@ -139,11 +150,7 @@ export const dateFileBrowserFilters: {
}
]
export const fileTypeFileBrowserFilters: {
id: string
label: IntlString<{}>
getType: () => any
}[] = [
export const fileTypeFileBrowserFilters: TypeFilter[] = [
{
id: 'typeAny',
label: attachment.string.FileBrowserTypeFilterAny,
@ -181,7 +188,7 @@ export const fileTypeFileBrowserFilters: {
}
]
export async function AddAttachmentToSaved(attach: Attachment): Promise<void> {
export async function AddAttachmentToSaved (attach: Attachment): Promise<void> {
const client = getClient()
await client.createDoc(attachment.class.SavedAttachments, preference.space.Preference, {
@ -189,7 +196,7 @@ export async function AddAttachmentToSaved(attach: Attachment): Promise<void> {
})
}
export async function DeleteAttachmentFromSaved(attach: Attachment): Promise<void> {
export async function DeleteAttachmentFromSaved (attach: Attachment): Promise<void> {
const client = getClient()
const current = await client.findOne(attachment.class.SavedAttachments, { attachedTo: attach._id })
@ -202,6 +209,7 @@ export default async (): Promise<Resources> => ({
component: {
AttachmentsPresenter,
AttachmentPresenter,
AttachmentGalleryPresenter,
Attachments,
FileBrowser,
Photos

View File

@ -28,6 +28,8 @@ export default mergeIds(attachmentId, attachment, {
Photos: '' as IntlString,
FileBrowser: '' as IntlString,
FileBrowserFileCounter: '' as IntlString,
FileBrowserListView: '' as IntlString,
FileBrowserGridView: '' as IntlString,
FileBrowserFilterFrom: '' as IntlString,
FileBrowserFilterIn: '' as IntlString,
FileBrowserFilterDate: '' as IntlString,

View File

@ -229,7 +229,7 @@
.container {
position: relative;
display: flex;
padding: .5rem 2rem;
padding: 0.5rem 2rem;
.avatar {
min-width: 2.25rem;