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

Add a directory navigation component #4473

Merged
merged 43 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1f1684e
add folder breadcrumbs
cstns Sep 6, 2024
a9d136c
add a updateVisibility api call
cstns Sep 6, 2024
2003125
add a rough draft of the visibility selector
cstns Sep 6, 2024
7686918
use the visibility selector
cstns Sep 6, 2024
905bf79
use the file breadcrumbs as source of truth and include all file/fold…
cstns Sep 6, 2024
38c72e5
display the baseUrl in the Folder breadcrumbs
cstns Sep 6, 2024
548e4a3
rework the filePath path to take into account the baseUrl paths where…
cstns Sep 9, 2024
31e61a8
add validation & styling to the visibilitySelector when selecting fol…
cstns Sep 9, 2024
cda482e
update the last directory in breadcrumbs to reflect visibility change
cstns Sep 9, 2024
cbe75c6
set the BaseURL itemFilePath in the FileBrowser
cstns Sep 9, 2024
cc6fc1b
prevent setting an empty static file path
cstns Sep 9, 2024
d417a7a
Merge remote-tracking branch 'origin/main' into 4449_add-a-directory-…
cstns Sep 9, 2024
3257c27
Merge branch '4449_add-a-directory-navigation-component' into 4461_ad…
cstns Sep 9, 2024
5e2d8bf
Add the URL column in the files table
cstns Sep 9, 2024
77b40e3
Add validation to the staticPFilePaths, disable current directory upd…
cstns Sep 9, 2024
104aad0
Add the current folder options in the static asset get files api
cstns Sep 10, 2024
3ec7e00
alter the assets client request to expose the folder payload
cstns Sep 10, 2024
7cc30d2
alter the assets tab to reload the current folder on instance restart
cstns Sep 10, 2024
12c2fd3
removed alert from improper place
cstns Sep 10, 2024
1b716fe
disable the folder breadcrumbs whilst the instance is loading
cstns Sep 10, 2024
be328d1
Merge branch 'main' into 4449_add-a-directory-navigation-component
cstns Sep 10, 2024
284a78a
Merge branch '4449_add-a-directory-navigation-component' into 4461_ad…
cstns Sep 10, 2024
314a147
qf failing e2e test
cstns Sep 10, 2024
5dbdd3f
Merge remote-tracking branch 'origin/4449_add-a-directory-navigation-…
cstns Sep 10, 2024
7fb2c7f
Merge branch 'refs/heads/4449_add-a-directory-navigation-component' i…
cstns Sep 10, 2024
27c2c14
Merge remote-tracking branch 'origin/4461_add-file-asset-service-visi…
cstns Sep 10, 2024
42148f1
add e2e tests
cstns Sep 10, 2024
ef7254f
adding leading slash when copying file paths
cstns Sep 10, 2024
e6e428a
use and display the fully qualified path name when copying and displa…
cstns Sep 10, 2024
de8eccb
display the leading slash for filePaths and use the fqdn for baseUrl's
cstns Sep 10, 2024
d8a7b03
rename baseUrl prop as it was conflicting with other props
cstns Sep 11, 2024
4fd5bfa
revert leading slashes and prepending file paths
cstns Sep 11, 2024
90eb0f6
remove prepended value for the breadcrumbs folder path
cstns Sep 11, 2024
6f76e9c
fix directory creation after single source of truth
cstns Sep 11, 2024
57757cb
avoid base url double slashes by clearing leading slashes on visibili…
cstns Sep 11, 2024
a4609b1
styling
cstns Sep 11, 2024
0be3bd3
fix e2e tests
cstns Sep 11, 2024
b7ada99
Merge pull request #4472 from FlowFuse/4461_add-file-asset-service-vi…
hardillb Sep 11, 2024
87fcd0f
Merge branch 'main' into 4449_add-a-directory-navigation-component
cstns Sep 16, 2024
48971b1
Merge branch 'main' into 4449_add-a-directory-navigation-component
hardillb Sep 17, 2024
881c403
Add padding between file path and "copy" button
joepavitt Sep 17, 2024
b9eb00e
extract copy functionality into distinct component, copy text on text…
cstns Sep 17, 2024
82b5128
Fix the "Copied!" CSS and gap between text and button
joepavitt Sep 18, 2024
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
18 changes: 18 additions & 0 deletions forge/ee/routes/staticAssets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ module.exports = async function (app) {
try {
const sharingConfig = await request.project.getSetting(KEY_SHARED_ASSETS) || {}
const result = await app.containers.listFiles(request.project, filePath)

result.files.forEach(file => {
if (file.type === 'directory') {
const absolutePath = filePath + (filePath.length > 0 ? '/' : '') + file.name
Expand All @@ -69,6 +70,23 @@ module.exports = async function (app) {
}
}
})

const parentDirectoryName = filePath.split('/').pop()
const parentDirectoryPath = filePath.split('/').slice(0, -1).join('/')

const parentFiles = await app.containers.listFiles(
request.project, parentDirectoryPath
)
const currentDirectory = parentFiles.files.filter(file => file.name === parentDirectoryName).shift()

if (currentDirectory) {
result.folder = currentDirectory
const absolutePath = parentDirectoryPath + (parentDirectoryPath.length > 0 ? '/' : '') + currentDirectory.name
if (sharingConfig[absolutePath]) {
result.folder.share = sharingConfig[absolutePath]
}
} else result.folder = null

reply.send(result)
} catch (err) {
if (err.statusCode === 404) {
Expand Down
14 changes: 13 additions & 1 deletion frontend/src/api/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ const getFiles = function (instanceId, path) {
// remove leading / from path
path = path.replace(/^\//, '')
return client.get(`/api/v1/projects/${instanceId}/files/_/${encodeURIComponent(path || '')}`).then(res => {
return res.data.files
return {
files: res.data.files,
folder: res.data.folder
}
})
}

Expand All @@ -30,6 +33,14 @@ const updateFolder = function (instanceId, pwd, oldName, newName) {
})
}

const updateVisibility = function (instanceId, pwd, visibility, staticPath = '') {
// remove leading / from path
pwd = pwd.replace(/^\//, '')
return client.put(`/api/v1/projects/${instanceId}/files/_/${encodeURIComponent(pwd)}`, {
share: visibility === 'public' ? { root: staticPath } : {}
})
}

const deleteItem = function (instanceId, path) {
// remove leading / from path
path = path.replace(/^\//, '')
Expand All @@ -52,6 +63,7 @@ export default {
getFiles,
createFolder,
updateFolder,
updateVisibility,
deleteItem,
uploadFile
}
62 changes: 62 additions & 0 deletions frontend/src/components/TextCopier.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<template>
<span class="ff-text-copier">
<span @click="copyPath">
<slot name="default">
<span class="text">{{ text }}</span>
</slot>
</span>
<DuplicateIcon v-if="text.length" class="ff-icon" @click="copyPath" @click.prevent.stop />
<span ref="copied" class="ff-copied">Copied!</span>
</span>
</template>

<script>
import { DuplicateIcon } from '@heroicons/vue/outline'

export default {
name: 'TextCopier',
components: { DuplicateIcon },
props: {
text: {
required: true,
type: String
}
},
methods: {
copyPath () {
navigator.clipboard.writeText(this.text)

// show "Copied" notification
this.$refs.copied.style.display = 'inline'
// hide after 500ms
setTimeout(() => {
this.$refs.copied.style.display = 'none'
}, 500)
}
}
}
</script>

<style scoped lang="scss">
.ff-text-copier {
display: inline-flex;
align-items: center;
gap: 3px;
position: relative;
&:hover {
cursor: pointer;
}
.ff-copied {
background-color: black;
color: white;
padding: 3px;
border-radius: 3px;
position: absolute;
margin-top: -3px;
margin-left: 3px;
display: none;
z-index: 100;
left: 100%;
}
}
</style>
59 changes: 47 additions & 12 deletions frontend/src/components/file-browser/FileBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,9 @@ export default {
required: true,
type: Object
},
folder: {
required: true,
type: Object
},
breadcrumbs: {
required: true,
type: Object
type: Array
},
disabled: {
required: false,
Expand All @@ -136,6 +132,10 @@ export default {
required: false,
default: '',
type: String
},
instance: {
required: true,
type: Object
}
},
emits: ['change-directory', 'items-updated'],
Expand All @@ -154,15 +154,36 @@ export default {
}
},
computed: {
folder () {
return [...this.breadcrumbs].pop()
},
isPublicFolder () {
if (!this.folder) {
return false
}

return Object.prototype.hasOwnProperty.call(this.folder, 'share') &&
Object.prototype.hasOwnProperty.call(this.folder.share, 'root')
},
publicFolderPath () {
if (!this.isPublicFolder) {
return null
}

return this.folder.share.root
},
instanceId () {
return this.$route.params.id
},
pwd () {
return [...this.breadcrumbs, this.folder.name].filter(b => b).join('/').replace('//', '/')
return [...this.breadcrumbs.map(crumb => crumb.name)]
.filter(b => b)
.join('/')
.replace('//', '/')
},
baseURI () {
// clear null values
const breadcrumbs = this.breadcrumbs.filter(n => n)
const breadcrumbs = this.breadcrumbs.map(crumb => crumb.name).filter(n => n)
return breadcrumbs.join('/').replace('//', '/')
},
columns () {
Expand Down Expand Up @@ -195,10 +216,25 @@ export default {
is: markRaw(ItemFilePath),
extraProps: {
breadcrumbs: this.breadcrumbs,
folder: this.folder.name || ''
folder: this.folder?.name || ''
}
}
},
{
key: 'url',
label: 'URL',
sortable: true,
component: {
is: markRaw(ItemFilePath),
extraProps: {
baseURL: this.instance?.url,
breadcrumbs: this.breadcrumbs,
prepend: this.publicFolderPath,
isNotAvailable: !this.isPublicFolder
}
},
hidden: true
},
{
key: 'lastModified',
label: 'Last Modified',
Expand All @@ -207,17 +243,16 @@ export default {
]
},
noDataMessages () {
return this.noDataMessage.length ? this.noDataMessage : `No files in '${this.folder.name || 'Storage'}'`
return this.noDataMessage.length ? this.noDataMessage : `No files in '${this.folder?.name || 'Storage'}'`
}
},
methods: {
showDialog (dialog) {
this.$refs[dialog].show()
},
createFolder () {
const pwd = this.baseURI + '/' + (this.folder.name || '')
this.loading = true
AssetsAPI.createFolder(this.instanceId, pwd, this.forms.newFolder.name)
AssetsAPI.createFolder(this.instanceId, this.baseURI, this.forms.newFolder.name)
.then(() => this.$emit('items-updated'))
.catch(error => {
console.error(error)
Expand Down Expand Up @@ -292,7 +327,7 @@ export default {
})
},
uploadFile () {
const pwd = this.baseURI + '/' + (this.folder.name || '')
const pwd = this.baseURI + '/'
const filename = this.forms.file.name
this.loading = true
AssetsAPI.uploadFile(this.instanceId, pwd, filename, this.forms.file)
Expand Down
Loading
Loading