Skip to content

Commit

Permalink
Make basic conflict resolving possible / Refactor drag & drop logic /…
Browse files Browse the repository at this point in the history
… Extend modal.js
  • Loading branch information
lookacat committed May 20, 2022
1 parent 2d82130 commit 43db91a
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 113 deletions.
204 changes: 164 additions & 40 deletions packages/web-app-files/src/helpers/resource/move.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,165 @@
import PQueue from 'p-queue'
import { basename, join } from 'path'

export const move = async (resourcesToMove, target, client) => {
console.log("move")
const errors = []
const movePromises = []
const moveQueue = new PQueue({ concurrency: 4 })
const itemsInTarget = await client.files.list(target.webDavPath, 1)

resourcesToMove.forEach((resource: any) => {
movePromises.push(
moveQueue.add(async () => {
const exists = itemsInTarget.some((e) => basename(e.name) === resource.name)
if (exists) {
const message = `Resource with name ${resource.name} already exists`
errors.push({
resourceName: resource.name,
message: message
})
return
}

try {
await client.files.move(
resource.webDavPath,
join(target.webDavPath, resource.name)
)
/*this.REMOVE_FILE(resource)
this.REMOVE_FILE_FROM_SEARCHED(resource)
this.REMOVE_FILE_SELECTION(resource)*/
} catch (error) {
console.error(error)
error.resourceName = resource.name
errors.push(error)
}
}))
console.log(errors)
import { Resource } from './index'
import { join } from 'path'

export enum ResolveStrategy {
CANCEL,
REPLACE,
MERGE,
KEEP_BOTH
}
export interface ResolveConflict {
strategy: ResolveStrategy
doForAllConflicts: boolean
}
export interface FileConflict {
resource: Resource
strategy?: ResolveStrategy
}

const resolveFileExists = (createModal, hideModal, resourceName, conflictCount) => {
return new Promise<ResolveConflict>((resolve) => {
let doForAllConflicts = false
const modal = {
variation: 'danger',
title: 'File already exists',
message: `Resource with name ${resourceName} already exists.`,
cancelText: 'Cancel',
confirmText: 'Replace',
checkbox: true,
checkboxLabel: `Do this for all ${conflictCount} conflicts`,
onCheckboxValueChanged: (value) => {
doForAllConflicts = value
},
onCancel: () => {
hideModal()
resolve({ strategy: ResolveStrategy.CANCEL, doForAllConflicts } as ResolveConflict)
},
onConfirm: () => {
hideModal()
resolve({ strategy: ResolveStrategy.REPLACE, doForAllConflicts } as ResolveConflict)
}
}
createModal(modal)
})
}
const resolveAllConflicts = async (
resourcesToMove,
targetFolder,
client,
createModal,
hideModal
) => {
const targetFolderItems = await client.files.list(targetFolder.webDavPath, 50)
const targetPath = targetFolder.path
const index = targetFolder.webDavPath.lastIndexOf(targetPath)
const webDavPrefix = targetFolder.webDavPath.substring(0, index)

// Collect all conflicting resources
const allConflicts = []
for (const resource of resourcesToMove) {
const potentialTargetWebDavPath = join(webDavPrefix, targetFolder.path, resource.path)
const exists = targetFolderItems.some((e) => e.name === potentialTargetWebDavPath)
if (exists) {
allConflicts.push({
resource,
strategy: null
} as FileConflict)
}
}
let count = 0
let doForAllConflicts = false
let doForAllConflictsStrategy = null
const resolvedConflicts = []
for (const conflict of allConflicts) {
// Resolve conflicts accordingly
if (doForAllConflicts) {
conflict.strategy = doForAllConflictsStrategy
resolvedConflicts.push(conflict)
continue
}

// Resolve next conflict
const conflictsLeft = allConflicts.length - count
const result: ResolveConflict = await resolveFileExists(
createModal,
hideModal,
conflict.resource.name,
conflictsLeft
)
conflict.strategy = result.strategy
resolvedConflicts.push(conflict)
count += 1

// User checked 'do for all x conflicts'
if (!result.doForAllConflicts) continue
doForAllConflicts = true
doForAllConflictsStrategy = result.strategy
}
return resolvedConflicts
}
export const move = async (
resourcesToMove,
targetFolder,
client,
createModal,
hideModal,
showMessage
) => {
const errors = []
const resolvedConflicts = await resolveAllConflicts(
resourcesToMove,
targetFolder,
client,
createModal,
hideModal
)
const movedResources = []

for (const resource of resourcesToMove) {
const hasConflict = resolvedConflicts.some((e) => e.resource.id === resource.id)
if (hasConflict) {
const resolveStrategy = resolvedConflicts.find((e) => e.resource.id === resource.id)?.strategy
if (resolveStrategy === ResolveStrategy.CANCEL) {
continue
}
if (resolveStrategy === ResolveStrategy.REPLACE) {
await client.files.delete(join(targetFolder.webDavPath, resource.name))
}
}
try {
await client.files.move(resource.webDavPath, join(targetFolder.webDavPath, resource.name))
movedResources.push(resource)
} catch (error) {
console.error(error)
error.resourceName = resource.name
errors.push(error)
}
}
// show error / success messages
let title
if (errors.length === 0) {
const count = resourcesToMove.length
title = `${count} item was moved successfully`
showMessage({
title,
status: 'success'
})
return movedResources
}

if (errors.length === 1) {
title = `Failed to move "${errors[0]?.resourceName}`
showMessage({
title,
status: 'danger'
})
await Promise.all(movePromises)
}
return movedResources
}

title = `Failed to move ${errors.length} resources`
showMessage({
title,
status: 'danger'
})
return movedResources
}
85 changes: 13 additions & 72 deletions packages/web-app-files/src/views/Personal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,8 @@ import NotFoundMessage from '../components/FilesList/NotFoundMessage.vue'
import ListInfo from '../components/FilesList/ListInfo.vue'
import Pagination from '../components/FilesList/Pagination.vue'
import ContextActions from '../components/FilesList/ContextActions.vue'
import { basename, join } from 'path'
import PQueue from 'p-queue'
import { createLocationSpaces } from '../router'
import { useResourcesViewDefaults } from '../composables'
import { fetchResources } from '../services/folder'
import { defineComponent } from '@vue/composition-api'
import { Resource, move } from '../helpers/resource'
import { useCapabilityShareJailEnabled } from 'web-pkg/src/composables'
Expand Down Expand Up @@ -220,7 +217,7 @@ export default defineComponent({
methods: {
...mapActions('Files', ['loadPreview']),
...mapActions(['showMessage']),
...mapActions(['showMessage', 'createModal', 'hideModal']),
...mapMutations('Files', ['REMOVE_FILE', 'REMOVE_FILE_FROM_SEARCHED', 'REMOVE_FILE_SELECTION']),
async fetchResources(path, properties) {
Expand All @@ -237,75 +234,19 @@ export default defineComponent({
const isTargetSelected = selected.some((e) => e.id === fileIdTarget)
if (isTargetSelected) return
if (targetInfo.type !== 'folder') return
const itemsInTarget = await this.fetchResources(targetInfo.webDavPath)
move(selected, targetInfo, this.$client)
/*
^ // try to move all selected files
const errors = []
const movePromises = []
const moveQueue = new PQueue({ concurrency: 4 })
selected.forEach((resource) => {
movePromises.push(
moveQueue.add(async () => {
const exists = itemsInTarget.some((e) => basename(e.name) === resource.name)
if (exists) {
const message = this.$gettext('Resource with name %{name} already exists')
errors.push({
resourceName: resource.name,
message: this.$gettextInterpolate(message, { name: resource.name }, true)
})
return
}
try {
await this.$client.files.move(
resource.webDavPath,
join(targetInfo.webDavPath, resource.name)
)
this.REMOVE_FILE(resource)
this.REMOVE_FILE_FROM_SEARCHED(resource)
this.REMOVE_FILE_SELECTION(resource)
} catch (error) {
console.error(error)
error.resourceName = resource.name
errors.push(error)
}
})
)
})
await Promise.all(movePromises)
// show error / success messages
let title
if (errors.length === 0) {
const count = selected.length
title = this.$ngettext(
'%{count} item was moved successfully',
'%{count} items were moved successfully',
count
)
this.showMessage({
title: this.$gettextInterpolate(title, { count }),
status: 'success'
})
return
}
if (errors.length === 1) {
title = this.$gettext('Failed to move "%{resourceName}"')
this.showMessage({
title: this.$gettextInterpolate(title, { resourceName: errors[0]?.resourceName }, true),
status: 'danger'
})
return
const movedResources = await move(
selected,
targetInfo,
this.$client,
this.createModal,
this.hideModal,
this.showMessage
)
for (const resource of movedResources) {
this.REMOVE_FILE(resource)
this.REMOVE_FILE_FROM_SEARCHED(resource)
this.REMOVE_FILE_SELECTION(resource)
}
title = this.$gettext('Failed to move %{count} resources')
this.showMessage({
title: this.$gettextInterpolate(title, { count: errors.length }),
status: 'danger'
})*/
},
rowMounted(resource, component) {
Expand Down
3 changes: 3 additions & 0 deletions packages/web-runtime/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
:button-cancel-text="modal.cancelText"
:button-confirm-text="modal.confirmText"
:button-confirm-disabled="modal.confirmDisabled || !!modal.inputError"
:checkbox="modal.checkbox"
:checkbox-label="modal.checkboxLabel"
@cancel="modal.onCancel"
@confirm="modal.onConfirm"
@input="modal.onInput"
@checkbox-changed="modal.onCheckboxValueChanged"
@mounted="focusModal"
@beforeDestroy="focusModal"
/>
Expand Down
8 changes: 7 additions & 1 deletion packages/web-runtime/src/store/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ const state = {
inputPlaceholder: '',
inputLabel: '',
inputError: '',
checkbox: false,
checkboxLabel: '',
// Events
onCancel: emptyReturn,
onConfirm: emptyReturn,
onInput: emptyReturn
onInput: emptyReturn,
onCheckboxValueChanged: emptyReturn
}

const actions = {
Expand Down Expand Up @@ -48,6 +51,8 @@ const mutations = {
state.icon = modal.icon
state.title = modal.title
state.message = modal.message
state.checkbox = modal.checkbox || false
state.checkboxLabel = modal.checkboxLabel || ''
state.cancelText = modal.cancelText || 'Cancel'
state.confirmText = modal.confirmText || 'Confirm'
state.confirmDisabled = modal.confirmDisabled || false
Expand All @@ -61,6 +66,7 @@ const mutations = {
state.inputError = modal.inputError || null
state.inputDisabled = modal.inputDisabled || false
state.onInput = modal.onInput || emptyReturn
state.onCheckboxValueChanged = modal.onCheckboxValueChanged || emptyReturn
},

HIDE_MODAL(state) {
Expand Down

0 comments on commit 43db91a

Please sign in to comment.