mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-28 05:56:02 +03:00
UBERF-7008 Show files and folders as a grid (#5845)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
ece0504d3c
commit
bb6f9d7645
@ -16,6 +16,7 @@
|
||||
import core, {
|
||||
type Blob,
|
||||
type Domain,
|
||||
type FindOptions,
|
||||
type Role,
|
||||
type RolesAssignment,
|
||||
type Type,
|
||||
@ -84,6 +85,11 @@ export class TResource extends TDoc implements Resource {
|
||||
@ReadOnly()
|
||||
file?: Ref<Blob>
|
||||
|
||||
@Prop(TypeRef(core.class.Blob), drive.string.Preview)
|
||||
@ReadOnly()
|
||||
@Hidden()
|
||||
preview?: Ref<Blob>
|
||||
|
||||
@Prop(TypeRef(drive.class.Resource), drive.string.Parent)
|
||||
@Index(IndexKind.Indexed)
|
||||
@ReadOnly()
|
||||
@ -107,6 +113,7 @@ export class TFolder extends TResource implements Folder {
|
||||
declare path: Ref<Folder>[]
|
||||
|
||||
declare file: undefined
|
||||
declare preview: undefined
|
||||
}
|
||||
|
||||
@Model(drive.class.File, drive.class.Resource, DOMAIN_DRIVE)
|
||||
@ -274,33 +281,86 @@ function defineResource (builder: Builder): void {
|
||||
},
|
||||
'createdBy'
|
||||
],
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||
options: {
|
||||
lookup: {
|
||||
file: core.class.Blob,
|
||||
preview: core.class.Blob
|
||||
},
|
||||
sort: {
|
||||
_class: SortingOrder.Descending
|
||||
}
|
||||
},
|
||||
} as FindOptions<Resource>,
|
||||
configOptions: {
|
||||
hiddenKeys: ['name', 'file', 'parent', 'path'],
|
||||
hiddenKeys: ['name', 'file', 'parent', 'path', 'type'],
|
||||
sortable: true
|
||||
}
|
||||
},
|
||||
drive.viewlet.FileTable
|
||||
)
|
||||
|
||||
// builder.createDoc<Viewlet>(
|
||||
// view.class.Viewlet,
|
||||
// core.space.Model,
|
||||
// {
|
||||
// attachTo: drive.class.Resource,
|
||||
// descriptor: drive.viewlet.Grid,
|
||||
// config: ['', 'type', 'size', 'lastModified', 'createdBy'],
|
||||
// configOptions: {
|
||||
// hiddenKeys: ['name', 'file', 'parent', 'path'],
|
||||
// sortable: true
|
||||
// }
|
||||
// },
|
||||
// drive.viewlet.FileGrid
|
||||
// )
|
||||
builder.createDoc(
|
||||
view.class.ViewletDescriptor,
|
||||
core.space.Model,
|
||||
{
|
||||
label: drive.string.Grid,
|
||||
icon: drive.icon.Grid,
|
||||
component: drive.component.GridView
|
||||
},
|
||||
drive.viewlet.Grid
|
||||
)
|
||||
|
||||
builder.createDoc<Viewlet>(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: drive.class.Resource,
|
||||
descriptor: drive.viewlet.Grid,
|
||||
viewOptions: {
|
||||
groupBy: [],
|
||||
orderBy: [
|
||||
['name', SortingOrder.Ascending],
|
||||
['$lookup.file.size', SortingOrder.Ascending],
|
||||
['$lookup.file.modifiedOn', SortingOrder.Descending]
|
||||
],
|
||||
other: []
|
||||
},
|
||||
config: [
|
||||
{
|
||||
key: '',
|
||||
presenter: drive.component.ResourcePresenter,
|
||||
label: drive.string.Name,
|
||||
sortingKey: 'name'
|
||||
},
|
||||
{
|
||||
key: '$lookup.file.size',
|
||||
presenter: drive.component.FileSizePresenter,
|
||||
label: drive.string.Size,
|
||||
sortingKey: '$lookup.file.size'
|
||||
},
|
||||
{
|
||||
key: '$lookup.file.modifiedOn',
|
||||
label: core.string.ModifiedDate
|
||||
},
|
||||
'createdBy'
|
||||
],
|
||||
configOptions: {
|
||||
hiddenKeys: ['name', 'file', 'parent', 'path'],
|
||||
sortable: true
|
||||
},
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||
options: {
|
||||
lookup: {
|
||||
file: core.class.Blob,
|
||||
preview: core.class.Blob
|
||||
},
|
||||
sort: {
|
||||
_class: SortingOrder.Descending
|
||||
}
|
||||
} as FindOptions<Resource>
|
||||
},
|
||||
drive.viewlet.FileGrid
|
||||
)
|
||||
}
|
||||
|
||||
function defineFolder (builder: Builder): void {
|
||||
|
@ -75,6 +75,7 @@ export default mergeIds(driveId, drive, {
|
||||
RenameFolder: '' as ViewAction
|
||||
},
|
||||
string: {
|
||||
Grid: '' as IntlString,
|
||||
Name: '' as IntlString,
|
||||
Description: '' as IntlString,
|
||||
Size: '' as IntlString,
|
||||
@ -84,6 +85,7 @@ export default mergeIds(driveId, drive, {
|
||||
Parent: '' as IntlString,
|
||||
Path: '' as IntlString,
|
||||
Drives: '' as IntlString,
|
||||
Download: '' as IntlString
|
||||
Download: '' as IntlString,
|
||||
Preview: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
@ -60,6 +60,7 @@
|
||||
"UpdateSpaceDescription": "Grants users ability to update the space",
|
||||
"ArchiveSpaceDescription": "Grants users ability to archive the space",
|
||||
"AutoJoin": "Auto join",
|
||||
"AutoJoinDescr": "Automatically join new employees to this space"
|
||||
"AutoJoinDescr": "Automatically join new employees to this space",
|
||||
"BlobSize": "Size"
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@
|
||||
"UpdateSpaceDescription": "Concede a los usuarios la capacidad de actualizar el espacio",
|
||||
"ArchiveSpaceDescription": "Concede a los usuarios la capacidad de archivar el espacio",
|
||||
"AutoJoin": "Auto unirse",
|
||||
"AutoJoinDescr": "Unirse automáticamente a los nuevos empleados a este espacio"
|
||||
"AutoJoinDescr": "Unirse automáticamente a los nuevos empleados a este espacio",
|
||||
"BlobSize": "Tamaño"
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@
|
||||
"UpdateSpaceDescription": "Concede aos usuários a capacidade de atualizar o espaço",
|
||||
"ArchiveSpaceDescription": "Concede aos usuários a capacidade de arquivar o espaço",
|
||||
"AutoJoin": "Auto adesão",
|
||||
"AutoJoinDescr": "Adesão automática de novos funcionários a este espaço"
|
||||
"AutoJoinDescr": "Adesão automática de novos funcionários a este espaço",
|
||||
"BlobSize": "Tamanho"
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,7 @@
|
||||
"UpdateSpaceDescription": "Дает пользователям разрешение обновлять пространство",
|
||||
"ArchiveSpaceDescription": "Дает пользователям разрешение архивировать пространство",
|
||||
"AutoJoin": "Автоприсоединение",
|
||||
"AutoJoinDescr": "Автоматически присоединять новых сотрудников к этому пространству"
|
||||
"AutoJoinDescr": "Автоматически присоединять новых сотрудников к этому пространству",
|
||||
"BlobSize": "Размер"
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,12 @@
|
||||
|
||||
export let value: Account
|
||||
export let avatarSize: IconSize = 'x-small'
|
||||
export let shouldShowAvatar: boolean = true
|
||||
export let shouldShowName: boolean = true
|
||||
export let disabled: boolean = false
|
||||
export let inline: boolean = false
|
||||
export let accent: boolean = false
|
||||
export let noUnderline: boolean = false
|
||||
export let compact = false
|
||||
|
||||
$: employee = $employeeByIdStore.get((value as PersonAccount)?.person as Ref<Employee>)
|
||||
@ -39,9 +42,31 @@
|
||||
{#if value}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
{#if employee}
|
||||
<EmployeePresenter value={employee} {disabled} {inline} {accent} {avatarSize} {compact} on:accent-color />
|
||||
<EmployeePresenter
|
||||
value={employee}
|
||||
{shouldShowAvatar}
|
||||
{shouldShowName}
|
||||
{disabled}
|
||||
{inline}
|
||||
{accent}
|
||||
{avatarSize}
|
||||
{noUnderline}
|
||||
{compact}
|
||||
on:accent-color
|
||||
/>
|
||||
{:else if person}
|
||||
<PersonPresenter value={person} {disabled} {inline} {accent} {avatarSize} {compact} on:accent-color />
|
||||
<PersonPresenter
|
||||
value={person}
|
||||
{shouldShowAvatar}
|
||||
{shouldShowName}
|
||||
{disabled}
|
||||
{inline}
|
||||
{accent}
|
||||
{avatarSize}
|
||||
{noUnderline}
|
||||
{compact}
|
||||
on:accent-color
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex-row-center">
|
||||
<Avatar size={avatarSize} />
|
||||
|
@ -22,14 +22,28 @@
|
||||
|
||||
export let value: Ref<PersonAccount>
|
||||
export let avatarSize: IconSize = 'x-small'
|
||||
export let shouldShowAvatar: boolean = true
|
||||
export let shouldShowName: boolean = true
|
||||
export let disabled: boolean = false
|
||||
export let inline: boolean = false
|
||||
export let accent: boolean = false
|
||||
export let noUnderline: boolean = false
|
||||
export let compact = false
|
||||
|
||||
$: account = $personAccountByIdStore.get(value)
|
||||
</script>
|
||||
|
||||
{#if account}
|
||||
<PersonAccountPresenter value={account} {disabled} {inline} {avatarSize} {accent} {compact} on:accent-color />
|
||||
<PersonAccountPresenter
|
||||
value={account}
|
||||
{shouldShowAvatar}
|
||||
{shouldShowName}
|
||||
{disabled}
|
||||
{inline}
|
||||
{avatarSize}
|
||||
{accent}
|
||||
{noUnderline}
|
||||
{compact}
|
||||
on:accent-color
|
||||
/>
|
||||
{/if}
|
||||
|
@ -2,6 +2,10 @@
|
||||
<symbol id="drive" viewBox="0 0 32 32">
|
||||
<path d="M16 3C10.7021 3 5 4.252 5 7V25C5 27.748 10.7021 29 16 29C21.2979 29 27 27.748 27 25V7C27 4.252 21.2979 3 16 3ZM16 5C21.7976 5 24.7949 6.4341 24.9968 7C24.7949 7.5659 21.7976 9 16 9C10.1587 9 7.1606 7.5444 7 7.0176V7.0127C7.1606 6.4556 10.1587 5 16 5ZM7 9.4277C9.1279 10.4951 12.6426 11 16 11C19.3574 11 22.8721 10.4951 25 9.4277V12.9873C24.8394 13.5444 21.8413 15 16 15C10.1499 15 7.1509 13.54 7 13V9.4277ZM7 15.4277C9.1279 16.4951 12.6426 17 16 17C19.3574 17 22.8721 16.4951 25 15.4277V18.9873C24.8394 19.5444 21.8413 21 16 21C10.1499 21 7.1509 19.54 7 19V15.4277ZM16 27C10.1499 27 7.1509 25.54 7 25V21.4277C9.1279 22.4951 12.6426 23 16 23C19.3574 23 22.8721 22.4951 25 21.4277V24.9873C24.8394 25.5444 21.8413 27 16 27Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
<symbol id="grid" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M7.5 12.5H3.75L3.75 16.25H7.5V12.5ZM7.5 3.75H3.75L3.75 7.5H7.5V3.75ZM16.25 12.5H12.5V16.25H16.25V12.5ZM16.25 3.75H12.5V7.5H16.25V3.75ZM3.75 2.5C3.05964 2.5 2.5 3.05964 2.5 3.75V7.5C2.5 8.19036 3.05964 8.75 3.75 8.75H7.5C8.19036 8.75 8.75 8.19036 8.75 7.5V3.75C8.75 3.05964 8.19036 2.5 7.5 2.5H3.75ZM3.75 11.25C3.05964 11.25 2.5 11.8096 2.5 12.5V16.25C2.5 16.9404 3.05964 17.5 3.75 17.5H7.5C8.19036 17.5 8.75 16.9404 8.75 16.25V12.5C8.75 11.8096 8.19036 11.25 7.5 11.25H3.75ZM11.25 12.5C11.25 11.8096 11.8096 11.25 12.5 11.25H16.25C16.9404 11.25 17.5 11.8096 17.5 12.5V16.25C17.5 16.9404 16.9404 17.5 16.25 17.5H12.5C11.8096 17.5 11.25 16.9404 11.25 16.25V12.5ZM12.5 2.5C11.8096 2.5 11.25 3.05964 11.25 3.75V7.5C11.25 8.19036 11.8096 8.75 12.5 8.75H16.25C16.9404 8.75 17.5 8.19036 17.5 7.5V3.75C17.5 3.05964 16.9404 2.5 16.25 2.5H12.5Z" />
|
||||
</symbol>
|
||||
<symbol id="file" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.5 KiB |
@ -2,6 +2,7 @@
|
||||
"string": {
|
||||
"Drive": "Drive",
|
||||
"Drives": "Drives",
|
||||
"Grid": "Grid",
|
||||
"File": "File",
|
||||
"Folder": "Folder",
|
||||
"Resource": "Resource",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"string": {
|
||||
"Drive": "Unidad",
|
||||
"Drives": "Unidades",
|
||||
"Grid": "Red",
|
||||
"File": "Archivo",
|
||||
"Folder": "Carpeta",
|
||||
"Resource": "Recurso",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"string": {
|
||||
"Drive": "Unidade",
|
||||
"Drives": "Unidades",
|
||||
"Grid": "Grade",
|
||||
"File": "Ficheiro",
|
||||
"Folder": "Pasta",
|
||||
"Resource": "Recurso",
|
||||
|
@ -2,6 +2,7 @@
|
||||
"string": {
|
||||
"Drive": "Диск",
|
||||
"Drives": "Диски",
|
||||
"Grid": "Сетка",
|
||||
"File": "Файл",
|
||||
"Folder": "Папка",
|
||||
"Resource": "Ресурс",
|
||||
|
@ -19,6 +19,7 @@ import drive from '@hcengineering/drive'
|
||||
const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(drive.icon, {
|
||||
Drive: `${icons}#drive`,
|
||||
Grid: `${icons}#grid`,
|
||||
File: `${icons}#file`,
|
||||
Folder: `${icons}#folder`,
|
||||
FolderOpen: `${icons}#folder-open`,
|
||||
|
149
plugins/drive-resources/src/components/GridItem.svelte
Normal file
149
plugins/drive-resources/src/components/GridItem.svelte
Normal file
@ -0,0 +1,149 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 core, { type WithLookup } from '@hcengineering/core'
|
||||
import { type Resource } from '@hcengineering/drive'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Button, IconMoreH } from '@hcengineering/ui'
|
||||
import { ObjectPresenter, TimestampPresenter, openDoc, showMenu } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import FileSizePresenter from './FileSizePresenter.svelte'
|
||||
import ResourcePresenter from './ResourcePresenter.svelte'
|
||||
import Thumbnail from './Thumbnail.svelte'
|
||||
|
||||
export let object: WithLookup<Resource>
|
||||
export let selected: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let hovered = false
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="card-container"
|
||||
class:selected
|
||||
class:hovered
|
||||
on:mouseover={() => dispatch('obj-focus', object)}
|
||||
on:mouseenter={() => dispatch('obj-focus', object)}
|
||||
on:focus={() => {}}
|
||||
on:contextmenu={(evt) => {
|
||||
showMenu(evt, { object })
|
||||
}}
|
||||
>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="card"
|
||||
on:click={() => {
|
||||
void openDoc(hierarchy, object)
|
||||
}}
|
||||
>
|
||||
<div class="card-content">
|
||||
<Thumbnail {object} size={'x-large'} />
|
||||
</div>
|
||||
|
||||
<div class="header flex-col p-2 pt-1">
|
||||
<div class="flex-row-center flex-gap-2 h-8">
|
||||
<div class="title overflow-label flex-grow">
|
||||
<ResourcePresenter value={object} shouldShowAvatar={false} accent />
|
||||
</div>
|
||||
<div class="tools">
|
||||
<Button
|
||||
icon={IconMoreH}
|
||||
kind="ghost"
|
||||
size="medium"
|
||||
on:click={(evt) => {
|
||||
hovered = true
|
||||
showMenu(evt, { object }, () => {
|
||||
hovered = false
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-between flex-gap-2 h-4">
|
||||
<div class="flex-row-center flex-gap-2 font-regular-12">
|
||||
<span class="flex-no-shrink">
|
||||
<ObjectPresenter
|
||||
_class={core.class.Account}
|
||||
objectId={object.createdBy}
|
||||
noUnderline
|
||||
props={{ avatarSize: 'tiny' }}
|
||||
/>
|
||||
</span>
|
||||
<span>•</span>
|
||||
<TimestampPresenter value={object.$lookup?.file?.modifiedOn ?? object.createdOn ?? object.modifiedOn} />
|
||||
</div>
|
||||
<div class="flex-no-shrink font-regular-12">
|
||||
<FileSizePresenter value={object.$lookup?.file?.size} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.card-container {
|
||||
position: relative;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--theme-divider-color);
|
||||
background-color: var(--theme-kanban-card-bg-color);
|
||||
|
||||
&.selected {
|
||||
outline: 2px solid var(--global-focus-BorderColor);
|
||||
outline-offset: 2px;
|
||||
background-color: var(--highlight-hover);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.hovered {
|
||||
.tools {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 0.5rem;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tools {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
border-top: 1px solid var(--theme-divider-color);
|
||||
padding: 0.25rem 0.5rem 0.5rem;
|
||||
}
|
||||
</style>
|
@ -13,6 +13,85 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, DocumentQuery, FindOptions, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { type Resource } from '@hcengineering/drive'
|
||||
import { ActionContext, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { BuildModelKey, ViewOptions } from '@hcengineering/view'
|
||||
import { ListSelectionProvider, SelectDirection, buildConfigLookup, focusStore } from '@hcengineering/view-resources'
|
||||
import GridItem from './GridItem.svelte'
|
||||
|
||||
export let _class: Ref<Class<Resource>>
|
||||
export let query: DocumentQuery<Resource>
|
||||
export let config: Array<BuildModelKey | string>
|
||||
export let options: FindOptions<Resource> | undefined = undefined
|
||||
export let viewOptions: ViewOptions
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const q = createQuery()
|
||||
|
||||
const listProvider = new ListSelectionProvider((offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection) => {
|
||||
if (dir === 'vertical') {
|
||||
let pos = objects.findIndex((p) => p._id === of?._id)
|
||||
pos += offset
|
||||
if (pos < 0) {
|
||||
pos = 0
|
||||
}
|
||||
if (pos >= objects.length) {
|
||||
pos = objects.length - 1
|
||||
}
|
||||
listProvider.updateFocus(objects[pos])
|
||||
}
|
||||
})
|
||||
|
||||
let objects: WithLookup<Resource>[] = []
|
||||
|
||||
$: orderBy = viewOptions.orderBy
|
||||
$: lookup = buildConfigLookup(hierarchy, _class, config, options?.lookup)
|
||||
|
||||
$: q.query(
|
||||
_class,
|
||||
query,
|
||||
(result) => {
|
||||
objects = result
|
||||
},
|
||||
{
|
||||
...options,
|
||||
sort: {
|
||||
...(options != null ? options.sort : {}),
|
||||
...(orderBy != null ? { [orderBy[0]]: orderBy[1] } : {})
|
||||
},
|
||||
lookup
|
||||
}
|
||||
)
|
||||
|
||||
$: listProvider.update(objects)
|
||||
$: selection = listProvider.current($focusStore)
|
||||
</script>
|
||||
|
||||
<div>Here will be grid view</div>
|
||||
<ActionContext context={{ mode: 'browser' }} />
|
||||
|
||||
<div class="grid-container">
|
||||
{#each objects as object, i}
|
||||
{@const selected = selection === i}
|
||||
<div class="grid-cell">
|
||||
<GridItem
|
||||
{object}
|
||||
{selected}
|
||||
on:obj-focus={(evt) => {
|
||||
listProvider.updateFocus(evt.detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.grid-container {
|
||||
display: grid;
|
||||
margin: 0.5rem 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
82
plugins/drive-resources/src/components/Thumbnail.svelte
Normal file
82
plugins/drive-resources/src/components/Thumbnail.svelte
Normal file
@ -0,0 +1,82 @@
|
||||
<!--
|
||||
// Copyright © 2024 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 WithLookup } from '@hcengineering/core'
|
||||
import drive, { type Resource } from '@hcengineering/drive'
|
||||
import { getBlobRef, getClient, sizeToWidth } from '@hcengineering/presentation'
|
||||
import { Icon, IconSize } from '@hcengineering/ui'
|
||||
|
||||
import IconFolderThumbnail from './icons/FolderThumbnail.svelte'
|
||||
|
||||
export let object: WithLookup<Resource>
|
||||
export let size: IconSize = 'x-large'
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
function extensionIconLabel (name: string): string {
|
||||
const parts = name.split('.')
|
||||
const ext = parts[parts.length - 1]
|
||||
return ext.substring(0, 4).toUpperCase()
|
||||
}
|
||||
|
||||
let isImage = false
|
||||
let isError = false
|
||||
|
||||
$: previewBlob = object.$lookup?.preview ?? object.$lookup?.file
|
||||
$: previewRef = object.$lookup?.preview !== undefined ? object.preview : object.file
|
||||
$: isImage = previewBlob?.contentType?.startsWith('image/') ?? false
|
||||
$: isFolder = hierarchy.isDerived(object._class, drive.class.Folder)
|
||||
</script>
|
||||
|
||||
{#if isFolder}
|
||||
<Icon icon={IconFolderThumbnail} size={'full'} fill={'var(--theme-trans-color)'} />
|
||||
{:else if previewBlob != null && previewRef != null && isImage && !isError}
|
||||
{#await getBlobRef(previewBlob, previewRef, object.name, sizeToWidth(size)) then blobSrc}
|
||||
<img
|
||||
class="img-fit"
|
||||
src={blobSrc.src}
|
||||
srcset={blobSrc.srcset}
|
||||
alt={object.name}
|
||||
on:error={() => {
|
||||
isError = true
|
||||
}}
|
||||
/>
|
||||
{/await}
|
||||
{:else}
|
||||
<div class="flex-center ext-icon">
|
||||
{extensionIconLabel(object.name)}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.ext-icon {
|
||||
flex-shrink: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.625rem;
|
||||
color: var(--primary-button-color);
|
||||
background-color: var(--primary-button-default);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.img-fit {
|
||||
object-fit: cover;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
// Copyright © 2024 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">
|
||||
export let fill: string = 'currentColor'
|
||||
export let size: 'small' | 'medium' | 'large' = 'small'
|
||||
</script>
|
||||
|
||||
<svg class="svg-{size}" {fill} width="32" height="32" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M426.666667 170.666667H170.666667c-47.36 0-85.333333 37.973333-85.333334 85.333333v512a85.333333 85.333333 0 0 0 85.333334 85.333333h682.666666a85.333333 85.333333 0 0 0 85.333334-85.333333V341.333333a85.333333 85.333333 0 0 0-85.333334-85.333333h-341.333333l-85.333333-85.333333z"
|
||||
/>
|
||||
</svg>
|
@ -39,6 +39,7 @@ export const drivePlugin = plugin(driveId, {
|
||||
},
|
||||
icon: {
|
||||
Drive: '' as Asset,
|
||||
Grid: '' as Asset,
|
||||
File: '' as Asset,
|
||||
Folder: '' as Asset,
|
||||
FolderOpen: '' as Asset,
|
||||
|
@ -25,6 +25,7 @@ export interface Drive extends TypedSpace {}
|
||||
export interface Resource extends Doc<Drive> {
|
||||
name: string
|
||||
file?: Ref<Blob>
|
||||
preview?: Ref<Blob>
|
||||
|
||||
parent: Ref<Resource>
|
||||
path: Ref<Resource>[]
|
||||
|
Loading…
Reference in New Issue
Block a user