Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[full-ci] Refactor CreateAndUpload into composables #8938

Merged
merged 26 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6787465
WIP
lookacat Apr 27, 2023
667e727
Make createFolder work
lookacat Apr 27, 2023
2a685cd
Fix unref files
lookacat Apr 27, 2023
a98ff9a
Refactor useFileCreateNewFolder, implement useFileCreateNewFile
lookacat Apr 27, 2023
f41a535
Implement composables into CreateAndUpload
lookacat Apr 27, 2023
4bc18e4
Refactor CreateAndUpload
lookacat Apr 27, 2023
69423d3
Reimplement addAppProviderFileFunc
lookacat Apr 27, 2023
55b9d7d
Fix import
lookacat Apr 27, 2023
07d5bf7
Make linter happy
lookacat Apr 27, 2023
a7282a3
PoC: let CreateNewFile composable accumulate available actions
JammingBen Apr 27, 2023
74f4174
Address PR issues
lookacat Apr 28, 2023
e375562
remove dev leftover
lookacat Apr 28, 2023
ad4b4bb
Add basic tests for file/folder name validation
lookacat Apr 28, 2023
cdfe227
Remove old CreateAndUpload.spec unittests
lookacat Apr 28, 2023
cf78597
Address PR issues
lookacat May 2, 2023
fa0a8cf
Address PR issue
lookacat May 2, 2023
c92da28
Implement working unittests for useFileActionsCreateNewFolder.spec.ts
lookacat May 2, 2023
8bfdfe3
Implement working unittests for useFileActionsCreateNewFile
lookacat May 2, 2023
100a8cb
Fix file extension and mime type fiel actions
JammingBen May 3, 2023
5791cef
Remove unrelated file
JammingBen May 3, 2023
3de5db8
Make linter happy
lookacat May 3, 2023
0945b4f
Fix ref error in unittest
lookacat May 3, 2023
b9d4b14
Fix space issue, fix locationspace generic
lookacat May 3, 2023
08c264f
Remove ref from space
lookacat May 3, 2023
8d4e6e5
Fix unittests
lookacat May 3, 2023
18a7a4c
Remove accidental change
lookacat May 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 55 additions & 280 deletions packages/web-app-files/src/components/AppBar/CreateAndUpload.vue

Large diffs are not rendered by default.

lookacat marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
/>
</div>
<oc-button
v-oc-tooltip="$gettext('Create link')"
class="oc-ml-s"
size="small"
v-oc-tooltip="$gettext('Create link')"
appearance="raw"
:aria-label="$gettext('Create link')"
@click="createQuickLink"
>
Expand Down
2 changes: 2 additions & 0 deletions packages/web-app-files/src/composables/actions/files/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ export * from './useFileActionsShowDetails'
export * from './useFileActionsShowEditTags'
export * from './useFileActionsShowShares'
export * from './useFileActionsCreateSpaceFromResource'
export * from './useFileActionsCreateNewFolder'
export * from './useFileActionsCreateNewFile'
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import { Resource, SpaceResource, extractNameWithoutExtension } from 'web-client/src/helpers'
import { Store } from 'vuex'
import { computed, Ref, unref } from 'vue'
import { useClientService, useRequest, useRouter, useStore } from 'web-pkg/src/composables'
import { FileAction, FileActionOptions } from 'web-pkg/src/composables/actions'
import { useGettext } from 'vue3-gettext'
import { resolveFileNameDuplicate } from 'web-app-files/src/helpers/resource'
import { join } from 'path'
import { WebDAV } from 'web-client/src/webdav'
import { isLocationSpacesActive } from 'web-app-files/src/router'
import { getIndicators } from 'web-app-files/src/helpers/statusIndicators'
import { EDITOR_MODE_CREATE, useFileActions } from './useFileActions'
import { urlJoin } from 'web-client/src/utils'
import { configurationManager } from 'web-pkg/src'
import { stringify } from 'qs'

export const useFileActionsCreateNewFile = ({
store,
space,
newFileHandlers,
mimetypesAllowedForCreation
}: {
store?: Store<any>
space?: SpaceResource
newFileHandlers?: Ref<any> // FIXME: type?
mimetypesAllowedForCreation?: Ref<any> // FIXME: type?
} = {}) => {
store = store || useStore()
const router = useRouter()
const { $gettext } = useGettext()
const { makeRequest } = useRequest()

const { openEditor, triggerDefaultAction } = useFileActions()
const clientService = useClientService()
const currentFolder = computed((): Resource => store.getters['Files/currentFolder'])
const files = computed((): Array<Resource> => store.getters['Files/files'])
const ancestorMetaData = computed(() => store.getters['Files/ancestorMetaData'])
const areFileExtensionsShown = computed((): boolean => store.state.Files.areFileExtensionsShown)

const capabilities = computed(() => store.getters['capabilities'])

const checkNewFileName = (fileName) => {
if (fileName === '') {
return $gettext('File name cannot be empty')
}

if (/[/]/.test(fileName)) {
return $gettext('File name cannot contain "/"')
}

if (fileName === '.') {
return $gettext('File name cannot be equal to "."')
}

if (fileName === '..') {
return $gettext('File name cannot be equal to ".."')
}

if (/\s+$/.test(fileName)) {
return $gettext('File name cannot end with whitespace')
}

const exists = unref(files).find((file) => file.name === fileName)

if (exists) {
return $gettext('%{name} already exists', { name: fileName }, true)
}

return null
}

const addAppProviderFileFunc = async (fileName) => {
// FIXME: this belongs in web-app-external, but the app provider handles file creation differently than other editor extensions. Needs more refactoring.
if (fileName === '') {
return
}
try {
const baseUrl = urlJoin(
configurationManager.serverUrl,
unref(capabilities).files.app_providers[0].new_url
)
const query = stringify({
parent_container_id: unref(currentFolder).fileId,
filename: fileName
})
const url = `${baseUrl}?${query}`
const response = await makeRequest('POST', url)
if (response.status !== 200) {
throw new Error(`An error has occurred: ${response.status}`)
}
const path = join(unref(currentFolder).path, fileName) || ''
const resource = await (clientService.webdav as WebDAV).getFileInfo(space, {
path
})
if (unref(loadIndicatorsForNewFile)) {
resource.indicators = getIndicators({ resource, ancestorMetaData: unref(ancestorMetaData) })
}
triggerDefaultAction({ space: space, resources: [resource] })
store.commit('Files/UPSERT_RESOURCE', resource)
store.dispatch('hideModal')
store.dispatch('showMessage', {
title: $gettext('"%{fileName}" was created successfully', { fileName })
})
} catch (error) {
console.error(error)
store.dispatch('showMessage', {
title: $gettext('Failed to create file'),
status: 'danger'
})
}
}

const loadIndicatorsForNewFile = computed(() => {
return isLocationSpacesActive(router, 'files-spaces-generic') && space.driveType !== 'share'
})

const addNewFile = async (fileName, openAction) => {
if (fileName === '') {
return
}

try {
const path = join(unref(currentFolder).path, fileName)
const resource = await (clientService.webdav as WebDAV).putFileContents(space, {
path
})

if (loadIndicatorsForNewFile.value) {
resource.indicators = getIndicators({ resource, ancestorMetaData: unref(ancestorMetaData) })
}

store.commit('Files/UPSERT_RESOURCE', resource)

if (openAction) {
openEditor(
openAction,
space.getDriveAliasAndItem(resource),
resource.webDavPath,
resource.fileId,
EDITOR_MODE_CREATE
)
store.dispatch('hideModal')

return
}

store.dispatch('hideModal')
store.dispatch('showMessage', {
title: $gettext('"%{fileName}" was created successfully', { fileName })
})
} catch (error) {
console.error(error)
store.dispatch('showMessage', {
title: $gettext('Failed to create file'),
status: 'danger'
})
}
}

const handler = (
fileActionOptions: FileActionOptions,
extension: string,
openAction: any // FIXME: type?
) => {
const checkInputValue = (value) => {
store.dispatch(
'setModalInputErrorMessage',
checkNewFileName(areFileExtensionsShown.value ? value : `${value}.${extension}`)
)
}
let defaultName = $gettext('New file') + `.${extension}`

if (unref(files).some((f) => f.name === defaultName)) {
defaultName = resolveFileNameDuplicate(defaultName, extension, unref(files))
}

if (!areFileExtensionsShown.value) {
defaultName = extractNameWithoutExtension({ name: defaultName, extension } as any)
}

const inputSelectionRange = !areFileExtensionsShown.value
? null
: [0, defaultName.length - (extension.length + 1)]

const modal = {
variation: 'passive',
title: $gettext('Create a new file'),
cancelText: $gettext('Cancel'),
confirmText: $gettext('Create'),
hasInput: true,
inputValue: defaultName,
inputLabel: $gettext('File name'),
inputError: checkNewFileName(
areFileExtensionsShown.value ? defaultName : `${defaultName}.${extension}`
),
inputSelectionRange,
onCancel: () => store.dispatch('hideModal'),
onConfirm: !openAction
? addAppProviderFileFunc
: (fileName) => {
if (!areFileExtensionsShown.value) {
fileName = `${fileName}.${extension}`
}
addNewFile(fileName, openAction)
},
onInput: checkInputValue
}

store.dispatch('createModal', modal)
}

const actions = computed((): FileAction[] => {
const actions = []
for (const newFileHandler of unref(newFileHandlers) || []) {
const openAction = newFileHandler.action
actions.push({
name: 'create-new-file',
icon: 'add',
Copy link
Collaborator

@JammingBen JammingBen Apr 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea for the future: In CreateAndUpload we currently load the icon via getIconResource. Would be nice to somehow handle this here. Maybe we can even remove ext: newFileHandler.ext then (which is only used for the icon).

handler: (args) => handler(args, newFileHandler.ext, openAction),
label: () => newFileHandler.menuTitle($gettext),
isEnabled: ({ resources }) => {
return true
},
canBeDefault: true,
componentType: 'button',
class: 'oc-files-actions-create-new-file',
ext: newFileHandler.ext
})
}
for (const mimeType of unref(mimetypesAllowedForCreation) || []) {
const openAction = false
actions.push({
name: 'create-new-file',
icon: 'add',
handler: (args) => handler(args, mimeType.ext, openAction),
label: () => mimeType.name,
isEnabled: ({ resources }) => {
return true
},
canBeDefault: true,
componentType: 'button',
class: 'oc-files-actions-create-new-file',
ext: mimeType.ext
})
}

return actions
})

return {
actions,
checkNewFileName,
addNewFile
}
}
Loading