Chunter: file browser additional fixes (#1547)

Signed-off-by: Ruslan Izhitsky <ruslan.izhitskiy@xored.com>
This commit is contained in:
Ruslan Izhitsky
2022-04-27 14:47:29 +06:00
committed by GitHub
parent 469ac85e99
commit ca2b5ba0f3
19 changed files with 658 additions and 261 deletions
@@ -16,127 +16,46 @@
import { Attachment } from '@anticrm/attachment'
import contact, { Employee } from '@anticrm/contact'
import { EmployeeAccount } from '@anticrm/contact'
import { Doc, getCurrentAccount, Ref, SortingOrder, SortingQuery, Space } from '@anticrm/core'
import { IntlString } from '@anticrm/platform'
import { getClient, UserBoxList } from '@anticrm/presentation'
import { DropdownLabelsIntl, IconMoreV, Label, Menu as UIMenu, showPopup } from '@anticrm/ui'
import core, { Class, Doc, getCurrentAccount, Ref, Space } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import ui, {
getCurrentLocation,
location,
IconMoreV,
IconSearch,
Label,
showPopup,
navigate,
EditWithIcon,
Spinner
} from '@anticrm/ui'
import { Menu } from '@anticrm/view-resources'
import { AttachmentPresenter } from '..'
import { onDestroy } from 'svelte'
import {
AttachmentPresenter,
FileBrowserSortMode,
dateFileBrowserFilters,
fileTypeFileBrowserFilters,
sortModeToOptionObject
} from '..'
import attachment from '../plugin'
enum SortMode {
NewestFile,
OldestFile,
AscendingAlphabetical,
DescendingAlphabetical
}
const msInDay = 24 * 60 * 60 * 1000
const getBeginningOfToday = () => {
const date = new Date()
date.setUTCHours(0, 0, 0, 0)
return date.getTime()
}
const dateObjects = [
{
id: 'dateAny',
label: attachment.string.FileBrowserDateFilterAny,
getDate: () => {
return undefined
}
},
{
id: 'dateToday',
label: attachment.string.FileBrowserDateFilterToday,
getDate: () => {
return { $gte: getBeginningOfToday() }
}
},
{
id: 'dateYesterday',
label: attachment.string.FileBrowserDateFilterYesterday,
getDate: () => {
return { $gte: getBeginningOfToday() - msInDay, $lt: getBeginningOfToday() }
}
},
{
id: 'date7Days',
label: attachment.string.FileBrowserDateFilter7Days,
getDate: () => {
return { $gte: getBeginningOfToday() - msInDay * 6 }
}
},
{
id: 'date30Days',
label: attachment.string.FileBrowserDateFilter30Days,
getDate: () => {
return { $gte: getBeginningOfToday() - msInDay * 29 }
}
},
{
id: 'date3Months',
label: attachment.string.FileBrowserDateFilter3Months,
getDate: () => {
return { $gte: getBeginningOfToday() - msInDay * 90 }
}
},
{
id: 'date12Months',
label: attachment.string.FileBrowserDateFilter12Months,
getDate: () => {
return { $gte: getBeginningOfToday() - msInDay * 364 }
}
}
]
const fileTypeObjects = [
{
id: 'typeAny',
label: attachment.string.FileBrowserTypeFilterAny,
getType: () => {
return undefined
}
},
{
id: 'typeImage',
label: attachment.string.FileBrowserTypeFilterImages,
getType: () => {
return { $like: '%image/%' }
}
},
{
id: 'typeAudio',
label: attachment.string.FileBrowserTypeFilterAudio,
getType: () => {
return { $like: '%audio/%' }
}
},
{
id: 'typeVideo',
label: attachment.string.FileBrowserTypeFilterVideos,
getType: () => {
return { $like: '%video/%' }
}
},
{
id: 'typePDF',
label: attachment.string.FileBrowserTypeFilterPDFs,
getType: () => {
return 'application/pdf'
}
}
]
import FileBrowserFilters from './FileBrowserFilters.svelte'
import FileBrowserSortMenu from './FileBrowserSortMenu.svelte'
const client = getClient()
export let space: Space | undefined
const loc = getCurrentLocation()
const spaceId: Ref<Space> | undefined = loc.query?.spaceId as Ref<Space> | undefined
export let requestedSpaceClasses: Ref<Class<Space>>[] = []
const currentUser = getCurrentAccount() as EmployeeAccount
let participants: Ref<Employee>[] = [currentUser.employee]
const assignee: Ref<Employee> | null = null
let selectedParticipants: Ref<Employee>[] = [currentUser.employee]
let selectedSpaces: Ref<Space>[] = []
let searchQuery: string = ''
let isLoading = false
let attachments: Attachment[] = []
let selectedFileNumber: number | undefined
let selectedSort: SortMode = SortMode.NewestFile
let selectedSort: FileBrowserSortMode = FileBrowserSortMode.NewestFile
let selectedDateId = 'dateAny'
let selectedFileTypeId = 'typeAny'
@@ -147,135 +66,89 @@
})
}
const showSortMenu = async (ev: Event): Promise<void> => {
showPopup(
UIMenu,
{
actions: [
{
label: sortModeToString(SortMode.NewestFile),
action: () => {
selectedSort = SortMode.NewestFile
}
},
{
label: sortModeToString(SortMode.OldestFile),
action: () => {
selectedSort = SortMode.OldestFile
}
},
{
label: sortModeToString(SortMode.AscendingAlphabetical),
action: () => {
selectedSort = SortMode.AscendingAlphabetical
}
},
{
label: sortModeToString(SortMode.DescendingAlphabetical),
action: () => {
selectedSort = SortMode.DescendingAlphabetical
}
}
]
},
ev.target as HTMLElement
)
}
$: fetch(searchQuery, selectedSort, selectedFileTypeId, selectedDateId, selectedParticipants, selectedSpaces)
const sortModeToString = (sortMode: SortMode): IntlString<{}> => {
switch (sortMode) {
case SortMode.NewestFile:
return attachment.string.FileBrowserSortNewest
case SortMode.OldestFile:
return attachment.string.FileBrowserSortOldest
case SortMode.AscendingAlphabetical:
return attachment.string.FileBrowserSortAZ
case SortMode.DescendingAlphabetical:
return attachment.string.FileBrowserSortZA
async function fetch(
searchQuery_: string,
selectedSort_: FileBrowserSortMode,
selectedFileTypeId_: string,
selectedDateId_: string,
selectedParticipants_: Ref<Employee>[],
selectedSpaces_: Ref<Space>[]
) {
isLoading = true
const nameQuery = searchQuery_ ? { name: { $like: '%' + searchQuery_ + '%' } } : {}
const accounts = await client.findAll(contact.class.EmployeeAccount, { employee: { $in: selectedParticipants_ } })
const senderQuery = accounts.length ? { modifiedBy: { $in: accounts.map((a) => a._id) } } : {}
let spaceQuery: { space: any }
if (selectedSpaces_.length) {
spaceQuery = { space: { $in: selectedSpaces_ } }
} else {
// nothing is selected in space filter - show all available attachments (except for the archived channels)
const allSpaces = await client.findAll(core.class.Space, {
archived: false,
_class: { $in: requestedSpaceClasses }
})
const availableSpaces = allSpaces
.filter((sp) => !sp.private || sp.members.includes(currentUser._id))
.map((sp) => sp._id)
spaceQuery = { space: { $in: availableSpaces } }
}
}
const sortModeToOptionObject = (sortMode: SortMode): SortingQuery<Attachment> => {
switch (sortMode) {
case SortMode.NewestFile:
return { modifiedOn: SortingOrder.Descending }
case SortMode.OldestFile:
return { modifiedOn: SortingOrder.Ascending }
case SortMode.AscendingAlphabetical:
return { name: SortingOrder.Ascending }
case SortMode.DescendingAlphabetical:
return { name: SortingOrder.Descending }
}
}
$: fetch(selectedSort, selectedFileTypeId, selectedDateId)
async function fetch (selectedSort_: SortMode, selectedFileTypeId_: string, selectedDateId_: string) {
const spaceQuery = space && { space: space._id }
const fileType = fileTypeObjects.find((o) => o.id === selectedFileTypeId_)?.getType()
const typeQuery = fileType && { type: fileType }
const date = dateObjects.find((o) => o.id === selectedDateId_)?.getDate()
const date = dateFileBrowserFilters.find((o) => o.id === selectedDateId_)?.getDate()
const dateQuery = date && { modifiedOn: date }
const fileType = fileTypeFileBrowserFilters.find((o) => o.id === selectedFileTypeId_)?.getType()
const fileTypeQuery = fileType && { type: fileType }
attachments = await client.findAll(
attachment.class.Attachment,
{ ...spaceQuery, ...typeQuery, ...dateQuery },
{ ...nameQuery, ...senderQuery, ...spaceQuery, ...dateQuery, ...fileTypeQuery },
{
sort: sortModeToOptionObject(selectedSort_)
sort: sortModeToOptionObject(selectedSort_),
limit: 200
}
)
isLoading = false
}
onDestroy(
location.subscribe(async (loc) => {
loc.query = undefined
navigate(loc)
})
)
</script>
<div class="ac-header full divide">
<div class="ac-header__wrap-title">
<span class="ac-header__title"><Label label={attachment.string.FileBrowser} /></span>
</div>
<EditWithIcon icon={IconSearch} bind:value={searchQuery} placeholder={ui.string.SearchDots} />
</div>
<div class="filterBlockContainer">
<div class="simpleFilterButton">
<UserBoxList
_class={contact.class.Employee}
items={participants}
label={attachment.string.FileBrowserFilterFrom}
on:update={(evt) => {
participants = evt.detail
}}
noItems={attachment.string.NoParticipants}
/>
</div>
<!-- TODO: wait for In filter -->
<!-- <div class="simpleFilterButton">
<UserBox _class={contact.class.Employee} label={attachment.string.FileBrowserFilterIn} bind:value={assignee} />
</div> -->
<div class="simpleFilterButton">
<DropdownLabelsIntl
items={dateObjects}
placeholder={attachment.string.FileBrowserFilterDate}
label={attachment.string.FileBrowserFilterDate}
bind:selected={selectedDateId}
/>
</div>
<div class="simpleFilterButton">
<DropdownLabelsIntl
items={fileTypeObjects}
placeholder={attachment.string.FileBrowserFilterFileType}
label={attachment.string.FileBrowserFilterFileType}
bind:selected={selectedFileTypeId}
/>
</div>
</div>
<FileBrowserFilters
{requestedSpaceClasses}
{spaceId}
bind:selectedParticipants
bind:selectedSpaces
bind:selectedDateId
bind:selectedFileTypeId
/>
<div class="group">
<div class="groupHeader">
<div class="eGroupHeaderCount">
<Label label={attachment.string.FileBrowserFileCounter} params={{ results: attachments?.length ?? 0 }} />
</div>
<div class="eGroupHeaderSortMenu" on:click={(event) => showSortMenu(event)}>
{'Sort: '}
<Label label={sortModeToString(selectedSort)} />
</div>
<FileBrowserSortMenu bind:selectedSort />
</div>
{#if attachments?.length}
{#if isLoading}
<div class="ml-4">
<Spinner />
</div>
{:else if attachments?.length}
<div class="flex-col">
{#each attachments as attachment, i}
<div class="flex-between attachmentRow" class:fixed={i === selectedFileNumber}>
@@ -299,9 +172,8 @@
<style lang="scss">
.group {
border: 1px solid var(--theme-bg-focused-border);
border-radius: 1rem;
padding: 1rem 0;
overflow: auto;
}
.groupHeader {
@@ -313,10 +185,6 @@
font-size: 0.75rem;
color: var(--theme-caption-color);
}
.eGroupHeaderSortMenu {
cursor: pointer;
}
}
.attachmentRow {
@@ -360,16 +228,4 @@
}
}
}
.filterBlockContainer {
display: flex;
flex-flow: row wrap;
margin-top: 10px;
margin-bottom: 10px;
}
.simpleFilterButton {
min-width: 4rem;
max-width: 12rem;
margin-left: 0.75rem;
}
</style>
@@ -0,0 +1,82 @@
<!--
// 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 contact, { Employee } from '@anticrm/contact'
import { Class, Ref, Space } from '@anticrm/core'
import { SpaceMultiBoxList, UserBoxList } from '@anticrm/presentation'
import { DropdownLabelsIntl } from '@anticrm/ui'
import attachment from '../plugin'
import { dateFileBrowserFilters, fileTypeFileBrowserFilters } from '..'
export let requestedSpaceClasses: Ref<Class<Space>>[]
export let spaceId: Ref<Space> | undefined
export let selectedParticipants: Ref<Employee>[]
export let selectedSpaces: Ref<Space>[]
export let selectedDateId: string
export let selectedFileTypeId: string
</script>
<div class="filterBlockContainer">
<div class="simpleFilterButton">
<UserBoxList
_class={contact.class.Employee}
items={selectedParticipants}
label={attachment.string.FileBrowserFilterFrom}
on:update={(evt) => {
selectedParticipants = evt.detail
}}
noItems={attachment.string.NoParticipants}
/>
</div>
<div class="simpleFilterButton">
<SpaceMultiBoxList
_classes={requestedSpaceClasses}
label={attachment.string.FileBrowserFilterIn}
selectedItems={spaceId ? [spaceId] : []}
on:update={(evt) => {
selectedSpaces = evt.detail
}}
/>
</div>
<div class="simpleFilterButton">
<DropdownLabelsIntl
items={dateFileBrowserFilters}
placeholder={attachment.string.FileBrowserFilterDate}
label={attachment.string.FileBrowserFilterDate}
bind:selected={selectedDateId}
/>
</div>
<div class="simpleFilterButton">
<DropdownLabelsIntl
items={fileTypeFileBrowserFilters}
placeholder={attachment.string.FileBrowserFilterFileType}
label={attachment.string.FileBrowserFilterFileType}
bind:selected={selectedFileTypeId}
/>
</div>
</div>
<style lang="scss">
.filterBlockContainer {
display: flex;
flex-flow: row wrap;
margin-top: 10px;
margin-bottom: 10px;
}
.simpleFilterButton {
max-width: 12rem;
margin-left: 0.75rem;
}
</style>
@@ -0,0 +1,83 @@
<script lang="ts">
import { IntlString } from '@anticrm/platform'
import { Label, Menu, showPopup } from '@anticrm/ui'
import { FileBrowserSortMode } from '..'
import attachment from '../plugin'
export let selectedSort: FileBrowserSortMode
const sortModeToString = (sortMode: FileBrowserSortMode): IntlString<{}> => {
switch (sortMode) {
case FileBrowserSortMode.NewestFile:
return attachment.string.FileBrowserSortNewest
case FileBrowserSortMode.OldestFile:
return attachment.string.FileBrowserSortOldest
case FileBrowserSortMode.AscendingAlphabetical:
return attachment.string.FileBrowserSortAZ
case FileBrowserSortMode.DescendingAlphabetical:
return attachment.string.FileBrowserSortZA
case FileBrowserSortMode.SmallestSize:
return attachment.string.FileBrowserSortSmallest
case FileBrowserSortMode.BiggestSize:
return attachment.string.FileBrowserSortBiggest
}
}
const showSortMenu = async (ev: Event): Promise<void> => {
showPopup(
Menu,
{
actions: [
{
label: sortModeToString(FileBrowserSortMode.NewestFile),
action: () => {
selectedSort = FileBrowserSortMode.NewestFile
}
},
{
label: sortModeToString(FileBrowserSortMode.OldestFile),
action: () => {
selectedSort = FileBrowserSortMode.OldestFile
}
},
{
label: sortModeToString(FileBrowserSortMode.AscendingAlphabetical),
action: () => {
selectedSort = FileBrowserSortMode.AscendingAlphabetical
}
},
{
label: sortModeToString(FileBrowserSortMode.DescendingAlphabetical),
action: () => {
selectedSort = FileBrowserSortMode.DescendingAlphabetical
}
},
{
label: sortModeToString(FileBrowserSortMode.SmallestSize),
action: () => {
selectedSort = FileBrowserSortMode.SmallestSize
}
},
{
label: sortModeToString(FileBrowserSortMode.BiggestSize),
action: () => {
selectedSort = FileBrowserSortMode.BiggestSize
}
}
]
},
ev.target as HTMLElement
)
}
</script>
<div class="sortMenu" on:click={(event) => showSortMenu(event)}>
<Label label={attachment.string.FileBrowserSort} />
<Label label={sortModeToString(selectedSort)} />
</div>
<style lang="scss">
.sortMenu {
cursor: pointer;
}
</style>
@@ -19,11 +19,25 @@
<svg class="svg-{size}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g {fill}>
<path d="M39.806,72.858h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.119c0-2.176,1.764-3.94,3.94-3.94h8.915 c2.176,0,3.94,1.764,3.94,3.94v37.799C43.746,71.094,41.982,72.858,39.806,72.858z"/>
<path d="M68.109,72.821h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.082c0-2.176,1.764-3.94,3.94-3.94h8.915 c2.176,0,3.94,1.764,3.94,3.94v37.799C72.049,71.057,70.285,72.821,68.109,72.821z"/>
<path d="M40.489,27.248c0.769,0.719,1.257,1.735,1.257,2.871v37.799c0,2.176-1.764,3.94-3.94,3.94h-8.915 c-0.234,0-0.46-0.03-0.683-0.069c0.704,0.658,1.643,1.069,2.683,1.069h8.915c2.176,0,3.94-1.764,3.94-3.94V31.119 C43.746,29.177,42.338,27.573,40.489,27.248z"/>
<path d="M68.792,27.211c0.769,0.719,1.257,1.735,1.257,2.871v37.799c0,2.176-1.764,3.94-3.94,3.94h-8.915 c-0.234,0-0.46-0.03-0.683-0.069c0.704,0.658,1.643,1.069,2.683,1.069h8.915c2.176,0,3.94-1.764,3.94-3.94V31.082 C72.049,29.14,70.641,27.535,68.792,27.211z"/>
<path d="M39.806,72.858h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.119 c0-2.176,1.764-3.94,3.94-3.94h8.915c2.176,0,3.94,1.764,3.94,3.94v37.799C43.746,71.094,41.982,72.858,39.806,72.858z" style="fill:none;stroke:#000000;stroke-miterlimit:10;"/>
<path d="M68.109,72.821h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.082 c0-2.176,1.764-3.94,3.94-3.94h8.915c2.176,0,3.94,1.764,3.94,3.94v37.799C72.049,71.057,70.285,72.821,68.109,72.821z" style="fill:none;stroke:#000000;stroke-miterlimit:10;"/>
<path
d="M39.806,72.858h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.119c0-2.176,1.764-3.94,3.94-3.94h8.915 c2.176,0,3.94,1.764,3.94,3.94v37.799C43.746,71.094,41.982,72.858,39.806,72.858z"
/>
<path
d="M68.109,72.821h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.082c0-2.176,1.764-3.94,3.94-3.94h8.915 c2.176,0,3.94,1.764,3.94,3.94v37.799C72.049,71.057,70.285,72.821,68.109,72.821z"
/>
<path
d="M40.489,27.248c0.769,0.719,1.257,1.735,1.257,2.871v37.799c0,2.176-1.764,3.94-3.94,3.94h-8.915 c-0.234,0-0.46-0.03-0.683-0.069c0.704,0.658,1.643,1.069,2.683,1.069h8.915c2.176,0,3.94-1.764,3.94-3.94V31.119 C43.746,29.177,42.338,27.573,40.489,27.248z"
/>
<path
d="M68.792,27.211c0.769,0.719,1.257,1.735,1.257,2.871v37.799c0,2.176-1.764,3.94-3.94,3.94h-8.915 c-0.234,0-0.46-0.03-0.683-0.069c0.704,0.658,1.643,1.069,2.683,1.069h8.915c2.176,0,3.94-1.764,3.94-3.94V31.082 C72.049,29.14,70.641,27.535,68.792,27.211z"
/>
<path
d="M39.806,72.858h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.119 c0-2.176,1.764-3.94,3.94-3.94h8.915c2.176,0,3.94,1.764,3.94,3.94v37.799C43.746,71.094,41.982,72.858,39.806,72.858z"
style="fill:none;stroke:#000000;stroke-miterlimit:10;"
/>
<path
d="M68.109,72.821h-8.915c-2.176,0-3.94-1.764-3.94-3.94V31.082 c0-2.176,1.764-3.94,3.94-3.94h8.915c2.176,0,3.94,1.764,3.94,3.94v37.799C72.049,71.057,70.285,72.821,68.109,72.821z"
style="fill:none;stroke:#000000;stroke-miterlimit:10;"
/>
</g>
</svg>
@@ -19,8 +19,15 @@
<svg class="svg-{size}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g {fill}>
<path d="M31.356,25.677l38.625,22.3c1.557,0.899,1.557,3.147,0,4.046l-38.625,22.3c-1.557,0.899-3.504-0.225-3.504-2.023V27.7 C27.852,25.902,29.798,24.778,31.356,25.677z"/>
<path d="M69.981,47.977l-38.625-22.3c-0.233-0.134-0.474-0.21-0.716-0.259l37.341,21.559c1.557,0.899,1.557,3.147,0,4.046 l-38.625,22.3c-0.349,0.201-0.716,0.288-1.078,0.301c0.656,0.938,1.961,1.343,3.078,0.699l38.625-22.3 C71.538,51.124,71.538,48.876,69.981,47.977z"/>
<path d="M31.356,25.677l38.625,22.3c1.557,0.899,1.557,3.147,0,4.046 l-38.625,22.3c-1.557,0.899-3.504-0.225-3.504-2.023V27.7C27.852,25.902,29.798,24.778,31.356,25.677z" style="fill:none;stroke:#000000;stroke-miterlimit:10;"/>
<path
d="M31.356,25.677l38.625,22.3c1.557,0.899,1.557,3.147,0,4.046l-38.625,22.3c-1.557,0.899-3.504-0.225-3.504-2.023V27.7 C27.852,25.902,29.798,24.778,31.356,25.677z"
/>
<path
d="M69.981,47.977l-38.625-22.3c-0.233-0.134-0.474-0.21-0.716-0.259l37.341,21.559c1.557,0.899,1.557,3.147,0,4.046 l-38.625,22.3c-0.349,0.201-0.716,0.288-1.078,0.301c0.656,0.938,1.961,1.343,3.078,0.699l38.625-22.3 C71.538,51.124,71.538,48.876,69.981,47.977z"
/>
<path
d="M31.356,25.677l38.625,22.3c1.557,0.899,1.557,3.147,0,4.046 l-38.625,22.3c-1.557,0.899-3.504-0.225-3.504-2.023V27.7C27.852,25.902,29.798,24.778,31.356,25.677z"
style="fill:none;stroke:#000000;stroke-miterlimit:10;"
/>
</g>
</svg>