Drive to cards (#6841)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2024-10-08 22:34:57 +05:00 committed by GitHub
parent 30c08f494b
commit 62e330d111
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 117 additions and 70 deletions

View File

@ -54,7 +54,7 @@ import {
TypeTimestamp,
UX
} from '@hcengineering/model'
import { TAttachedDoc, TDoc, TType, TTypedSpace } from '@hcengineering/model-core'
import { TAttachedDoc, TCard, TType, TTypedSpace } from '@hcengineering/model-core'
import presentation from '@hcengineering/model-presentation'
import print from '@hcengineering/model-print'
import tracker from '@hcengineering/model-tracker'
@ -84,19 +84,19 @@ export class TDefaultDriveTypeData extends TDrive implements RolesAssignment {
[key: Ref<Role>]: Ref<Account>[]
}
@Model(drive.class.Resource, core.class.Doc, DOMAIN_DRIVE)
@Model(drive.class.Resource, core.class.Card, DOMAIN_DRIVE)
@UX(drive.string.Resource)
export class TResource extends TDoc implements Resource {
export class TResource extends TCard implements Resource {
declare space: Ref<Drive>
@Prop(TypeString(), drive.string.Name)
@Index(IndexKind.FullText)
name!: string
declare title: string
@Prop(TypeRef(drive.class.Resource), drive.string.Parent)
@Index(IndexKind.Indexed)
@ReadOnly()
parent!: Ref<Resource>
declare parent: Ref<Resource>
@Prop(TypeRef(drive.class.Resource), drive.string.Path)
@ReadOnly()
@ -169,7 +169,7 @@ export class TFileVersion extends TAttachedDoc implements FileVersion {
@Prop(TypeString(), drive.string.Name)
@Index(IndexKind.FullText)
name!: string
title!: string
@Prop(TypeRef(core.class.Blob), drive.string.File)
@ReadOnly()

View File

@ -14,7 +14,7 @@
//
import core, { type Blob, type Ref, DOMAIN_BLOB, generateId, toIdMap } from '@hcengineering/core'
import type { File, FileVersion } from '@hcengineering/drive'
import type { Drive, File, FileVersion, Resource } from '@hcengineering/drive'
import {
type MigrateOperation,
type MigrationClient,
@ -56,8 +56,8 @@ async function migrateFileVersions (client: MigrationClient): Promise<void> {
collection: 'versions',
modifiedOn: file.modifiedOn,
modifiedBy: file.modifiedBy,
space: file.space,
name: exfile.name,
space: file.space as Ref<Drive>,
title: exfile.title,
file: blob._id,
size: blob.size,
lastModified: blob.modifiedOn,
@ -86,12 +86,52 @@ async function migrateFileVersions (client: MigrationClient): Promise<void> {
}
}
async function renameFields (client: MigrationClient): Promise<void> {
const resources = await client.find<Resource>(DOMAIN_DRIVE, {
_class: { $in: [drive.class.Resource, drive.class.File, drive.class.Folder] },
name: { $exists: true }
})
for (const resource of resources) {
await client.update(
DOMAIN_DRIVE,
{ _id: resource._id },
{
$rename: {
name: 'title'
}
}
)
}
const versions = await client.find<FileVersion>(DOMAIN_DRIVE, {
_class: drive.class.FileVersion,
name: { $exists: true }
})
for (const version of versions) {
await client.update(
DOMAIN_DRIVE,
{ _id: version._id },
{
$rename: {
name: 'title'
}
}
)
}
}
export const driveOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await tryMigrate(client, driveId, [
{
state: 'file-versions',
func: migrateFileVersions
},
{
state: 'renameFields',
func: renameFields
}
])
},

View File

@ -43,14 +43,14 @@ export function createModel (builder: Builder): void {
builder.mixin(drive.class.File, core.class.Class, serverCore.mixin.SearchPresenter, {
searchConfig: {
icon: drive.icon.File,
title: 'name'
title: 'title'
}
})
builder.mixin(drive.class.Folder, core.class.Class, serverCore.mixin.SearchPresenter, {
searchConfig: {
icon: drive.icon.Folder,
title: 'name'
title: 'title'
}
})
}

View File

@ -76,7 +76,7 @@ export interface Doc<S extends Space = Space> extends Obj {
export interface Card extends Doc {
title: string
description: CollaborativeDoc | null
description?: CollaborativeDoc | null
identifier?: string
parent?: Ref<Card> | null
}

View File

@ -53,7 +53,7 @@
}
const data: Omit<Data<Folder>, 'path'> = {
name: getTitle(name),
title: getTitle(name),
parent: _parent ?? drive.ids.Root
}

View File

@ -39,7 +39,7 @@
let descendants: Map<Ref<Folder>, Folder[]> = new Map<Ref<Folder>, Folder[]>()
function getDescendants (obj: Ref<Folder>): Ref<Folder>[] {
return (descendants.get(obj) ?? []).sort((a, b) => a.name.localeCompare(b.name)).map((p) => p._id)
return (descendants.get(obj) ?? []).sort((a, b) => a.title.localeCompare(b.title)).map((p) => p._id)
}
let selected: Ref<Doc> | undefined
@ -134,7 +134,7 @@
_id={folder._id}
folderIcon
iconProps={{ fill: 'var(--global-accent-IconColor)' }}
title={folder.name}
title={folder.title}
selected
isFold
empty

View File

@ -42,7 +42,7 @@
</script>
{#if object !== undefined && version !== undefined && blob !== undefined && contentType !== undefined}
<FilePreview file={blob} {contentType} name={version.name} metadata={version.metadata} fit />
<FilePreview file={blob} {contentType} name={version.title} metadata={version.metadata} fit />
{#if object.versions > 1}
<div class="w-full mt-6">

View File

@ -13,14 +13,19 @@
// limitations under the License.
-->
<script lang="ts">
import { type Folder } from '@hcengineering/drive'
import { type Drive, type Folder } from '@hcengineering/drive'
import { type Ref } from '@hcengineering/core'
import FolderBrowser from './FolderBrowser.svelte'
export let object: Folder
export let readonly: boolean = false
function getSpace (object: Folder): Ref<Drive> {
return object.space as Ref<Drive>
}
</script>
{#if object}
<FolderBrowser space={object.space} parent={object._id} {readonly} type={'folder'} />
<FolderBrowser space={getSpace(object)} parent={object._id} {readonly} type={'folder'} />
{/if}

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { type Ref } from '@hcengineering/core'
import { type Ref, type Space } from '@hcengineering/core'
import { type Drive, type Folder } from '@hcengineering/drive'
import { uploadFilesToDrive } from '../utils'

View File

@ -77,7 +77,7 @@
async (uuid, name, file, path, metadata) => {
const data = {
file: uuid,
name,
title: name,
size: file.size,
type: file.type,
lastModified: file instanceof File ? file.lastModified : Date.now(),
@ -108,7 +108,7 @@
</svelte:fragment>
<svelte:fragment slot="utils">
<a class="no-line" href={getFileUrl(version.file, object.name)} download={object.name} bind:this={download}>
<a class="no-line" href={getFileUrl(version.file, object.title)} download={object.title} bind:this={download}>
<Button
icon={IconDownload}
iconProps={{ size: 'medium' }}

View File

@ -42,14 +42,14 @@
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
{:else if type === 'link'}
<DocNavLink object={value} {disabled} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.title) }}>
{#if shouldShowAvatar}
<div class="icon">
<Icon {icon} size={'small'} />
</div>
{/if}
<div class="label nowrap flex flex-gap-2" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
<span>{value.name}</span>
<span>{value.title}</span>
{#if shouldShowVersion}
<span></span>
<span>{formatFileVersion(value.version)}</span>
@ -58,8 +58,8 @@
</div>
</DocNavLink>
{:else if type === 'text'}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{value.name}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.title) }}>
{value.title}
</span>
{/if}
{/if}

View File

@ -30,6 +30,6 @@
<Icon {icon} size={'medium'} />
</div>
<span class="overflow-label">
{value.name}
{value.title}
</span>
</div>

View File

@ -43,7 +43,7 @@
{
file: value.file,
contentType: value.type,
name: value.name,
name: value.title,
metadata: value.metadata
},
'centered'

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { type Doc, type DocumentQuery, type Ref, type WithLookup } from '@hcengineering/core'
import { type Doc, type DocumentQuery, type Space, type Ref, type WithLookup } from '@hcengineering/core'
import drive, { type Drive, type Folder } from '@hcengineering/drive'
import { Scroller, SearchInput, Panel, Button, IconMoreH } from '@hcengineering/ui'
import view, { Viewlet, ViewOptions } from '@hcengineering/view'

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { type Ref } from '@hcengineering/core'
import drive, { type Folder } from '@hcengineering/drive'
import drive, { Drive, type Folder } from '@hcengineering/drive'
import { createQuery } from '@hcengineering/presentation'
import { showMenu } from '@hcengineering/view-resources'
@ -34,11 +34,13 @@
$: query.query(drive.class.Folder, { _id }, (res) => {
;[object] = res
})
$: space = object?.space as Ref<Drive>
</script>
{#if object}
<FolderBrowser
space={object.space}
{space}
parent={object._id}
{object}
{embedded}

View File

@ -37,20 +37,20 @@
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
{:else if type === 'link'}
<DocNavLink {disabled} object={value} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.title) }}>
{#if shouldShowAvatar}
<div class="icon">
<Icon icon={FolderIcon} size={'small'} fill="var(--global-accent-IconColor)" />
</div>
{/if}
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
{value.name}
{value.title}
</span>
</div>
</DocNavLink>
{:else if type === 'text'}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{value.name}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.title) }}>
{value.title}
</span>
{/if}
{/if}

View File

@ -26,6 +26,6 @@
<Icon icon={drive.icon.Folder} size={'medium'} />
</div>
<span class="overflow-label">
{value.name}
{value.title}
</span>
</div>

View File

@ -33,7 +33,7 @@
const dispatch = createEventDispatcher()
function getDescendants (obj: Ref<Folder>): Ref<Folder>[] {
return (descendants.get(obj) ?? []).sort((a, b) => a.name.localeCompare(b.name)).map((p) => p._id)
return (descendants.get(obj) ?? []).sort((a, b) => a.title.localeCompare(b.title)).map((p) => p._id)
}
async function getActions (obj: Folder): Promise<Action[]> {
@ -67,7 +67,7 @@
<TreeItem
_id={doc._id}
folderIcon
title={doc.name}
title={doc.title}
selected={selected === doc._id}
isFold
empty={desc.length === 0}

View File

@ -32,7 +32,7 @@
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
let space: Ref<Drive> = value.space
let space: Ref<Drive> = value.space as Ref<Drive>
let parent: Ref<Folder> = value.parent as Ref<Folder>
async function save (): Promise<void> {

View File

@ -44,13 +44,13 @@
{#if isFolder}
<Icon icon={IconFolderThumbnail} size={'full'} fill={'var(--global-no-priority-PriorityColor)'} />
{:else if previewRef != null && isImage && !isError}
{#await getBlobRef(previewRef, object.name, sizeToWidth(size)) then blobSrc}
{#await getBlobRef(previewRef, object.title, sizeToWidth(size)) then blobSrc}
<img
draggable="false"
class="img-fit"
src={blobSrc.src}
srcset={blobSrc.srcset}
alt={object.name}
alt={object.title}
on:error={() => {
isError = true
}}
@ -58,7 +58,7 @@
{/await}
{:else}
<div class="flex-center ext-icon">
{extensionIconLabel(object.name)}
{extensionIconLabel(object.title)}
</div>
{/if}

View File

@ -44,14 +44,14 @@ import { restoreFileVersion, showCreateFolderPopup, showRenameResourcePopup } fr
const toFileObjectSearchResult = (e: WithLookup<File>): ObjectSearchResult => ({
doc: e,
title: e.name,
title: e.title,
icon: drive.icon.File,
component: FileSearchItem
})
const toFolderObjectSearchResult = (e: WithLookup<Folder>): ObjectSearchResult => ({
doc: e,
title: e.name,
title: e.title,
icon: drive.icon.Folder,
component: FolderSearchItem
})
@ -62,7 +62,7 @@ async function queryFile (
search: string,
filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }
): Promise<ObjectSearchResult[]> {
const q: DocumentQuery<File> = { name: { $like: `%${search}%` } }
const q: DocumentQuery<File> = { title: { $like: `%${search}%` } }
if (filter?.in !== undefined || filter?.nin !== undefined) {
q._id = {}
if (filter.in !== undefined) {
@ -81,7 +81,7 @@ async function queryFolder (
search: string,
filter?: { in?: RelatedDocument[], nin?: RelatedDocument[] }
): Promise<ObjectSearchResult[]> {
const q: DocumentQuery<Folder> = { name: { $like: `%${search}%` } }
const q: DocumentQuery<Folder> = { title: { $like: `%${search}%` } }
if (filter?.in !== undefined || filter?.nin !== undefined) {
q._id = {}
if (filter.in !== undefined) {
@ -111,12 +111,12 @@ async function DownloadFile (doc: WithLookup<File> | Array<WithLookup<File>>): P
for (const file of files) {
const version = file.$lookup?.file
if (version != null) {
const href = getFileUrl(version.file, version.name)
const href = getFileUrl(version.file, version.title)
const link = document.createElement('a')
link.style.display = 'none'
link.target = '_blank'
link.href = href
link.download = file.name
link.download = file.title
link.click()
}
}

View File

@ -13,16 +13,16 @@
// limitations under the License.
//
import { type Class, type Doc, type Ref, toIdMap } from '@hcengineering/core'
import {
import { type Class, type Doc, type Ref, type Space, toIdMap } from '@hcengineering/core'
import drive, {
type Drive,
type FileVersion,
type Folder,
type Resource,
createFile,
createFolder,
DriveEvents
} from '@hcengineering/drive'
import drive, { createFile } from '@hcengineering/drive'
import { type Asset, setPlatformStatus, unknownError } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { type AnySvelteComponent, showPopup } from '@hcengineering/ui'
@ -38,12 +38,12 @@ import CreateDrive from './components/CreateDrive.svelte'
import CreateFolder from './components/CreateFolder.svelte'
import RenamePopup from './components/RenamePopup.svelte'
import { Analytics } from '@hcengineering/analytics'
import FileTypeAudio from './components/icons/FileTypeAudio.svelte'
import FileTypeImage from './components/icons/FileTypeImage.svelte'
import FileTypeVideo from './components/icons/FileTypeVideo.svelte'
import FileTypePdf from './components/icons/FileTypePdf.svelte'
import FileTypeText from './components/icons/FileTypeText.svelte'
import { Analytics } from '@hcengineering/analytics'
import FileTypeVideo from './components/icons/FileTypeVideo.svelte'
async function navigateToDoc (_id: Ref<Doc>, _class: Ref<Class<Doc>>): Promise<void> {
const client = getClient()
@ -58,7 +58,7 @@ export function formatFileVersion (version: number): string {
}
export async function showCreateFolderPopup (
space: Ref<Drive> | undefined,
space: Ref<Space> | undefined,
parent: Ref<Folder>,
open = false
): Promise<void> {
@ -82,10 +82,10 @@ export async function showEditDrivePopup (drive: Drive): Promise<void> {
}
export async function showRenameResourcePopup (resource: Resource): Promise<void> {
showPopup(RenamePopup, { value: resource.name, format: 'text' }, undefined, async (res) => {
if (res != null && res !== resource.name) {
showPopup(RenamePopup, { value: resource.title }, undefined, async (res) => {
if (res != null && res !== resource.title) {
const client = getClient()
await client.update(resource, { name: res })
await client.update(resource, { title: res })
}
})
}
@ -159,7 +159,7 @@ export async function resolveParents (object: Resource): Promise<Doc[]> {
}
}
const root = await client.findOne(drive.class.Drive, { _id: object.space })
const root = await client.findOne(drive.class.Drive, { _id: object.space as Ref<Drive> })
if (root !== undefined) {
parents.push(root)
}
@ -203,7 +203,7 @@ async function fileUploadCallback (space: Ref<Drive>, parent: Ref<Folder>): Prom
const query = parent !== drive.ids.Root ? { space, path: parent } : { space }
const folders = await client.findAll(drive.class.Folder, query)
const foldersByName = new Map(folders.map((folder) => [folder.name, folder]))
const foldersByName = new Map(folders.map((folder) => [folder.title, folder]))
const findParent = async (path: string | undefined): Promise<Ref<Folder>> => {
if (path == null || path.length === 0) {
@ -217,16 +217,16 @@ async function fileUploadCallback (space: Ref<Drive>, parent: Ref<Folder>): Prom
let current = parent
while (segments.length > 1) {
const name = segments.shift()
if (name !== undefined) {
let folder = foldersByName.get(name)
const title = segments.shift()
if (title !== undefined) {
let folder = foldersByName.get(title)
if (folder !== undefined) {
current = folder._id
} else {
current = await createFolder(client, space, { name, parent: current })
current = await createFolder(client, space, { title, parent: current })
folder = await client.findOne(drive.class.Folder, { _id: current })
if (folder !== undefined) {
foldersByName.set(folder.name, folder)
foldersByName.set(folder.title, folder)
}
}
}
@ -242,7 +242,7 @@ async function fileUploadCallback (space: Ref<Drive>, parent: Ref<Folder>): Prom
size: file.size,
type: file.type,
lastModified: file instanceof File ? file.lastModified : Date.now(),
name,
title: name,
metadata
}

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import { AttachedDoc, Blob, CollectionSize, Doc, Ref, Type, TypedSpace } from '@hcengineering/core'
import { AttachedDoc, Blob, Card, CollectionSize, Ref, Type, TypedSpace } from '@hcengineering/core'
import drive from './plugin'
@ -26,8 +26,8 @@ export function TypeFileVersion (): Type<number> {
export interface Drive extends TypedSpace {}
/** @public */
export interface Resource extends Doc<Drive> {
name: string
export interface Resource extends Card {
title: string
parent: Ref<Resource>
path: Ref<Resource>[]
@ -57,7 +57,7 @@ export interface File extends Resource {
/** @public */
export interface FileVersion extends AttachedDoc<File, 'versions', Drive> {
name: string
title: string
file: Ref<Blob>
size: number
type: string

View File

@ -51,7 +51,7 @@ export async function createFile (
const versionId: Ref<FileVersion> = generateId()
const fileId = await client.createDoc(drive.class.File, space, {
name: data.name,
title: data.title,
parent,
path,
file: versionId,

View File

@ -64,7 +64,7 @@ export class WorkspaceClient {
if (blob !== undefined) {
const data = {
file: uuid as Ref<Blob>,
name,
title: name,
size: blob.size,
type: blob.contentType,
lastModified: blob.modifiedOn,