mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
UBERF-7524 Move files and folders in drive (#6016)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
8eea8f8ccd
commit
fa82ee1939
@ -45,7 +45,7 @@ import {
|
|||||||
import { TDoc, TTypedSpace } from '@hcengineering/model-core'
|
import { TDoc, TTypedSpace } from '@hcengineering/model-core'
|
||||||
import print from '@hcengineering/model-print'
|
import print from '@hcengineering/model-print'
|
||||||
import tracker from '@hcengineering/model-tracker'
|
import tracker from '@hcengineering/model-tracker'
|
||||||
import view, { type Viewlet, createAction } from '@hcengineering/model-view'
|
import view, { type Viewlet, actionTemplates, createAction } from '@hcengineering/model-view'
|
||||||
import workbench from '@hcengineering/model-workbench'
|
import workbench from '@hcengineering/model-workbench'
|
||||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||||
|
|
||||||
@ -410,6 +410,23 @@ function defineFolder (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
drive.action.RenameFolder
|
drive.action.RenameFolder
|
||||||
)
|
)
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
...actionTemplates.move,
|
||||||
|
action: view.actionImpl.ShowPopup,
|
||||||
|
actionProps: {
|
||||||
|
component: drive.component.MoveResource,
|
||||||
|
element: 'top',
|
||||||
|
fillProps: {
|
||||||
|
_object: 'value'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
target: drive.class.Folder,
|
||||||
|
context: {
|
||||||
|
mode: ['browser', 'context'],
|
||||||
|
group: 'tools'
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineFile (builder: Builder): void {
|
function defineFile (builder: Builder): void {
|
||||||
@ -488,6 +505,23 @@ function defineFile (builder: Builder): void {
|
|||||||
},
|
},
|
||||||
drive.action.RenameFile
|
drive.action.RenameFile
|
||||||
)
|
)
|
||||||
|
|
||||||
|
createAction(builder, {
|
||||||
|
...actionTemplates.move,
|
||||||
|
action: view.actionImpl.ShowPopup,
|
||||||
|
actionProps: {
|
||||||
|
component: drive.component.MoveResource,
|
||||||
|
element: 'top',
|
||||||
|
fillProps: {
|
||||||
|
_object: 'value'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
target: drive.class.File,
|
||||||
|
context: {
|
||||||
|
mode: ['browser', 'context'],
|
||||||
|
group: 'tools'
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function defineApplication (builder: Builder): void {
|
function defineApplication (builder: Builder): void {
|
||||||
|
@ -43,6 +43,7 @@ export default mergeIds(driveId, drive, {
|
|||||||
FolderPresenter: '' as AnyComponent,
|
FolderPresenter: '' as AnyComponent,
|
||||||
FilePresenter: '' as AnyComponent,
|
FilePresenter: '' as AnyComponent,
|
||||||
FileSizePresenter: '' as AnyComponent,
|
FileSizePresenter: '' as AnyComponent,
|
||||||
|
MoveResource: '' as AnyComponent,
|
||||||
ResourcePresenter: '' as AnyComponent
|
ResourcePresenter: '' as AnyComponent
|
||||||
},
|
},
|
||||||
function: {
|
function: {
|
||||||
|
@ -113,9 +113,6 @@
|
|||||||
allowDeselect
|
allowDeselect
|
||||||
showNavigate={false}
|
showNavigate={false}
|
||||||
docProps={{ disabled: true, noUnderline: true }}
|
docProps={{ disabled: true, noUnderline: true }}
|
||||||
on:object={(evt) => {
|
|
||||||
console.log('selected', evt)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
|
107
plugins/drive-resources/src/components/MoveResource.svelte
Normal file
107
plugins/drive-resources/src/components/MoveResource.svelte
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<!--
|
||||||
|
// 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 { Ref } from '@hcengineering/core'
|
||||||
|
import type { Drive, Folder, Resource } from '@hcengineering/drive'
|
||||||
|
import presentation, { Card, getClient, SpaceSelector } from '@hcengineering/presentation'
|
||||||
|
import view from '@hcengineering/view'
|
||||||
|
import { ObjectBox } from '@hcengineering/view-resources'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
|
import drive from '../plugin'
|
||||||
|
import { moveResources } from '../utils'
|
||||||
|
|
||||||
|
import DrivePresenter from './DrivePresenter.svelte'
|
||||||
|
import ResourcePresenter from './ResourcePresenter.svelte'
|
||||||
|
|
||||||
|
export let value: Resource
|
||||||
|
|
||||||
|
const client = getClient()
|
||||||
|
const hierarchy = client.getHierarchy()
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let space: Ref<Drive> = value.space
|
||||||
|
let parent: Ref<Folder> = value.parent as Ref<Folder>
|
||||||
|
|
||||||
|
async function save (): Promise<void> {
|
||||||
|
await moveResources([value], space, parent ?? drive.ids.Root)
|
||||||
|
}
|
||||||
|
|
||||||
|
let children: Ref<Folder>[] = []
|
||||||
|
$: void updateChildren(value)
|
||||||
|
|
||||||
|
async function updateChildren (resource: Resource): Promise<void> {
|
||||||
|
children = await findChildren(resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findChildren (resource: Resource): Promise<Array<Ref<Folder>>> {
|
||||||
|
if (hierarchy.isDerived(resource._class, drive.class.Folder)) {
|
||||||
|
const children = await client.findAll(
|
||||||
|
drive.class.Folder,
|
||||||
|
{ space: resource.space, path: resource._id as Ref<Folder> },
|
||||||
|
{ projection: { _id: 1 } }
|
||||||
|
)
|
||||||
|
|
||||||
|
return children.map((p) => p._id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
$: canSave = space !== value.space || parent !== value.parent
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
label={view.string.Move}
|
||||||
|
okAction={save}
|
||||||
|
okLabel={presentation.string.Save}
|
||||||
|
fullSize
|
||||||
|
{canSave}
|
||||||
|
on:close={() => dispatch('close')}
|
||||||
|
on:changeContent
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="header">
|
||||||
|
<ResourcePresenter {value} shouldShowAvatar={false} noUnderline />
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<div class="flex-row-center gap-2 min-w-100">
|
||||||
|
<SpaceSelector
|
||||||
|
bind:space
|
||||||
|
_class={drive.class.Drive}
|
||||||
|
label={drive.string.Drive}
|
||||||
|
component={DrivePresenter}
|
||||||
|
iconWithEmoji={view.ids.IconWithEmoji}
|
||||||
|
defaultIcon={drive.icon.Drive}
|
||||||
|
kind={'regular'}
|
||||||
|
size={'small'}
|
||||||
|
on:change={() => {
|
||||||
|
parent = drive.ids.Root
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ObjectBox
|
||||||
|
bind:value={parent}
|
||||||
|
_class={drive.class.Folder}
|
||||||
|
label={drive.string.Root}
|
||||||
|
docQuery={{ space }}
|
||||||
|
kind={'regular'}
|
||||||
|
size={'small'}
|
||||||
|
searchField={'name'}
|
||||||
|
allowDeselect={true}
|
||||||
|
showNavigate={false}
|
||||||
|
docProps={{ disabled: true, noUnderline: true }}
|
||||||
|
excluded={[value._id, ...children]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
@ -32,6 +32,7 @@ import FileSizePresenter from './components/FileSizePresenter.svelte'
|
|||||||
import FolderPanel from './components/FolderPanel.svelte'
|
import FolderPanel from './components/FolderPanel.svelte'
|
||||||
import FolderPresenter from './components/FolderPresenter.svelte'
|
import FolderPresenter from './components/FolderPresenter.svelte'
|
||||||
import GridView from './components/GridView.svelte'
|
import GridView from './components/GridView.svelte'
|
||||||
|
import MoveResource from './components/MoveResource.svelte'
|
||||||
import ResourcePresenter from './components/ResourcePresenter.svelte'
|
import ResourcePresenter from './components/ResourcePresenter.svelte'
|
||||||
|
|
||||||
import { getDriveLink, getFileLink, getFolderLink, resolveLocation } from './navigation'
|
import { getDriveLink, getFileLink, getFolderLink, resolveLocation } from './navigation'
|
||||||
@ -109,6 +110,7 @@ export default async (): Promise<Resources> => ({
|
|||||||
FolderPanel,
|
FolderPanel,
|
||||||
FolderPresenter,
|
FolderPresenter,
|
||||||
GridView,
|
GridView,
|
||||||
|
MoveResource,
|
||||||
ResourcePresenter
|
ResourcePresenter
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
|
@ -98,6 +98,38 @@ export async function renameResource (resource: Resource): Promise<void> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function moveResources (resources: Resource[], space: Ref<Drive>, parent: Ref<Folder>): Promise<void> {
|
||||||
|
const client = getClient()
|
||||||
|
|
||||||
|
const folder = parent !== drive.ids.Root ? await client.findOne(drive.class.Folder, { _id: parent }) : undefined
|
||||||
|
|
||||||
|
const path = folder !== undefined ? [folder._id, ...folder.path] : []
|
||||||
|
|
||||||
|
const folders = resources.filter((p) => p._class === drive.class.Folder).map((p) => p._id)
|
||||||
|
const children = await client.findAll(drive.class.Resource, { path: { $in: folders } })
|
||||||
|
const byParent = new Map<Ref<Resource>, Resource[]>()
|
||||||
|
for (const child of children) {
|
||||||
|
const group = byParent.get(child.parent) ?? []
|
||||||
|
group.push(child)
|
||||||
|
byParent.set(child.parent, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ops = client.apply(parent)
|
||||||
|
|
||||||
|
for (const resource of resources) {
|
||||||
|
await ops.update(resource, { space, parent, path })
|
||||||
|
|
||||||
|
const children = byParent.get(resource._id) ?? []
|
||||||
|
for (const child of children) {
|
||||||
|
// remove old path and add new path
|
||||||
|
const childPath = [...child.path.filter((p) => !resource.path.includes(p)), ...path]
|
||||||
|
await ops.update(child, { space, path: childPath })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await ops.commit()
|
||||||
|
}
|
||||||
|
|
||||||
const fileTypesMap: Record<string, AnySvelteComponent> = {
|
const fileTypesMap: Record<string, AnySvelteComponent> = {
|
||||||
'application/pdf': FileTypePdf,
|
'application/pdf': FileTypePdf,
|
||||||
audio: FileTypeAudio,
|
audio: FileTypeAudio,
|
||||||
|
Loading…
Reference in New Issue
Block a user